11.12链表

11/12

每日一题:3258. 统计满足 K 约束的子字符串数量 I

思路:滑动窗口

  • 常规做法:双循环

首先这题自己是定义一个check()函数,如果满足字符子串t中的0、1的个数均大于k,这不满足条件,计数res ++。

然后遍历截取s的子串,每次check(),用总的字符串个数n*(n+1)-res即可。

有个细节是:i从0开始,j要从i +k - 1开始,因为长度<=k的字符串必然满足条件。

  • 进阶做法:滑动窗口

计算以 0 为右端点的合法子串个数,以 1 为右端点的合法子串个数,……,以 n−1 为右端点的合法子串个数。

我们需要知道以 i 为右端点的合法子串,其左端点最小是多少

由于随着 i 的变大,窗口内的字符数量变多,越不能满足题目要求,所以最小左端点会随着 i 的增大而增大,有单调性,因此可以用 滑动窗口 计算。

设以 i 为右端点的合法子串,其左端点最小是 $ l_i$ 。那么以 i 为右端点的合法子串,其左端点可以是 $ l_i\(,\) l_i + 1$
······i,一共有 \(i - l_i + 1\)种结果,累加到答案中。

细节:字符 0 的 ASCII 值是偶数,字符 1 的 ASCII 值是奇数,所以可以用 ASCII 值 c mod 2 得到对应的数字。这也等价于和 1 计算 AND。

if(t[i] == '0') vec[0]++;
else if(t[i] == '1') vec[1] ++;
//也相当于 vec[t[i] & 1] ++;
class Solution {
private:
    bool check(string t , int k){
      vector<int> vec(2 ,0);
      for (int i = 0; i < t.size(); i++) {
         vec[t[i] & 1] ++;// if(t[i] == '0') vec[0]++;
                          //else if(t[i] == '1') vec[1] ++;
      }
      if(vec[0] > k && vec[1] > k)  return true;
      return false;
    }

public:
    int countKConstraintSubstrings(string s, int k) {
        int n = s.size();
        int res = 0;       
        for (int i = 0; i < n; i++) {
           for (int j = i + k - 1; j < n; j++) {
              if(check(s.substr(i , j - i + 1) , k))  res ++;
           }
        }
        return n * (n + 1) / 2 - res;
    }
};

滑动窗口法:

class Solution {
public:
    int countKConstraintSubstrings(string s, int k) {
        int ans = 0, left = 0, cnt[2]{};
        for (int i = 0; i < s.length(); i++) {
            cnt[s[i] & 1]++;
            while (cnt[0] > k && cnt[1] > k) {
                cnt[s[left] & 1]--;
                left++;
            }
            ans += i - left + 1;
        }
        return ans;
    }
};
  • 时间复杂度:O(n),其中 ns 的长度。注意 l 只会增加不会减少,所以二重循环的时间复杂度为 O(n)。
  • 空间复杂度:O(1)。

203.移除链表元素

C++的定义链表节点方式,如下所示:

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

不定义构造函数,C++默认生成一个构造函数,但是这个构造函数不会初始化任何成员变量,下面举两个例子:

通过自己定义构造函数初始化节点:

ListNode* head = new ListNode(5);

使用默认构造函数初始化节点:

ListNode* head = new ListNode();
head->val = 5;

所以如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值

定义虚拟头结点dummyHead的方式:

ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;

然后定义cur指针指向当前节点,从虚拟头结点开始遍历链表:

ListNode* cur = dummyHead;
while(cur->next != NULL)

如果cur的下一个节点值等于val,先用tmp指针记录下要删除的位置,然后cur的next指针跳过它,最后删除tmp节点。

if(cur->next->val == val){
  ListNode* tmp = cur->next;
  cur->next = cur->next->next;
  delete tmp;
}

最后一步把虚拟头结点删除,返回链表=返回头结点head,因为head可能变了,因此需重新定义。

head = dummyHead->next;
delete dummyHead;
return head;
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead -> next = head;

        ListNode* cur = dummyHead;
        while(cur->next != NULL){
          if(cur->next->val == val){
            ListNode* tmp = cur->next;
            cur->next = cur->next->next;
            delete tmp;
          }
          else cur = cur->next;
        }
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

707.设计链表

思路:定义虚拟头结点,其余的靠熟练度,多写多练。

class MyLinkedList {
public:

  struct LinkedNode {
    int val;
    LinkedNode* next;
    LinkedNode(int val):val(val) , next(nullptr){}
  };

    MyLinkedList() {
      dummyHead = new LinkedNode(0);
      size  = 0;  
    }
    
    int get(int index) {
        if (index > (size - 1) || index < 0){
          return  -1;
        }
        LinkedNode* cur = dummyHead -> next;
        while(index --){
          cur= cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        //插入的操作
        newNode->next = dummyHead->next;
        dummyHead->next = newNode;
        size ++;
    }
    
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = dummyHead;
        while(cur->next != nullptr) cur = cur->next;
        cur->next = newNode;
        size ++;
    }
    
    void addAtIndex(int index, int val) {
        if(index > size)  return;
        if(index < 0) index = 0;
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = dummyHead;
        while(index --)  cur = cur->next;
        newNode->next = cur->next;
        cur->next = newNode;
        size ++;
    }
    
    void deleteAtIndex(int index) {
        if(index >= size || index < 0)  return;
        LinkedNode* cur = dummyHead;
        while(index --) cur = cur->next;
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        tmp = nullptr;
        size --; 
    }

private:
    int size;
    LinkNode* dummyHead;
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

206.反转链表

思路:三种方法,其中双指针和递归法比较常见,要掌握。从后往前翻转比较抽象。

//双指针法
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
      ListNode* tmp;
      ListNode* cur = head;
      ListNode* pre = NULL;
      while(cur){
        tmp = cur->next;
        cur->next = pre;
        pre = cur;
        cur = tmp;
      }
      return pre;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
//递归法
class Solution {
public:
    ListNode* reverse(ListNode* pre , ListNode* cur){
      if(!cur) return pre;
      ListNode* tmp = cur->next;
      cur->next = pre;
      return reverse(cur , tmp);
    }
    
    ListNode* reverseList(ListNode* head) {
        return reverse(NULL ,head);
    }
};
  • 时间复杂度: O(n), 要递归处理链表的每个节点
  • 空间复杂度: O(n), 递归调用了 n 层栈空间
//从后往前翻转指针指向
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
      if(!head) return NULL;
      if(head->next == NULL) return head;
      //可以合写为
      //if(!head ||!head->next)  return head;
      ListNode* last = reverseList(head->next);
      head->next->next = head;
      head->next = NULL;
      return last;
    }
};

法三的详细解释:

  /**
     * 以链表1->2->3->4->5举例
     * @param head
     * @return
     */
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            /*
                直到当前节点的下一个节点为空时返回当前节点
                由于5没有下一个节点了,所以此处返回节点5
             */
            return head;
        }
        //递归传入下一个节点,目的是为了到达最后一个节点
        ListNode newHead = reverseList(head.next);
                /*
            第一轮出栈,head为5,head.next为空,返回5
            第二轮出栈,head为4,head.next为5,执行head.next.next=head也就是5.next=4,
                      把当前节点的子节点的子节点指向当前节点
                      此时链表为1->2->3->4<->5,由于4与5互相指向,所以此处要断开4.next=null
                      此时链表为1->2->3->4<-5
                      返回节点5
            第三轮出栈,head为3,head.next为4,执行head.next.next=head也就是4.next=3,
                      此时链表为1->2->3<->4<-5,由于3与4互相指向,所以此处要断开3.next=null
                      此时链表为1->2->3<-4<-5
                      返回节点5
            第四轮出栈,head为2,head.next为3,执行head.next.next=head也就是3.next=2,
                      此时链表为1->2<->3<-4<-5,由于2与3互相指向,所以此处要断开2.next=null
                      此时链表为1->2<-3<-4<-5
                      返回节点5
            第五轮出栈,head为1,head.next为2,执行head.next.next=head也就是2.next=1,
                      此时链表为1<->2<-3<-4<-5,由于1与2互相指向,所以此处要断开1.next=null
                      此时链表为1<-2<-3<-4<-5
                      返回节点5
            出栈完成,最终头节点5->4->3->2->1
         */
        head.next.next = head;
        head.next = null;
        return newHead;
    }
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

24. 两两交换链表中的节点

思路:模拟

24.两两交换链表中的节点1

24.两两交换链表中的节点3

初始时设 cur = dummyHead , ListNode* tmp = cur->next(记录节点1),ListNode* tmp1 = cur->next->next->next(记录节点3)

要保存1、3节点是因为步骤1会切断cur->next,步骤2会切断节点2->next

步骤一:cur->next = cur->next->next;//现在cur->next已经变成2了

步骤二:cur->next->next = tmp; //2->next = tmp = 1,现在cur->next->next指向1

步骤三:cur->next->next->next = tmp1 //1 -> next = tmp1 = 3

后移:之后每次将cur后移2个单位。因为交换相邻元素每两个元素是一个单位。

最后别忘记删除虚拟头结点,返回的是dummyHead -> next

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        while(cur->next != NULL && cur->next->next != NULL){
          ListNode* tmp = cur->next;  //1
          ListNode* tmp1 = cur->next->next->next; // 3

          cur->next = cur->next->next;//  cur -> 2
          cur->next->next = tmp; //    2 -> 1
          cur->next->next->next = tmp1; // 1 -> 3

          cur = cur->next->next;
        }
        ListNode* res = dummyHead->next;
        delete dummyHead;
        return res;
    }
};
posted @ 2024-11-12 21:51  七龙猪  阅读(1)  评论(0)    收藏  举报
-->