11.21生日-快慢指针

每日一题:3248. 矩阵中的蛇

思路:直接遍历字符串数组,根本不用定义grid。。。

class Solution {
public:
    int finalPositionOfSnake(int n, vector<string>& commands) {
    //  int grid[n][n] = {0};
    //  int cnt = 0;
    //     for (int i = 0; i < n; i++) {
    //        for (int j = 0; j < n; j++) {
    //           grid[i][j] = cnt++;             
    //        }
    //     }
      int res = 0;
      for (auto string : commands){
        if(string == "RIGHT") res += 1;
        if(string == "LEFT") res -= 1;
        if(string == "UP") res -= n;
        if(string == "DOWN") res += n;
      }
      return res;
    }
};

1.6 快慢指针系列

876. 链表的中间结点

思路:快慢指针

image-20241121130033540

对应题目样例的两种情况,while的条件应该为(fast && fast->next),样例2中fast走到5后还能往前走一步走到空,此时slow恰好对应4节点。

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
};

2095. 删除链表的中间节点

思路:找中间节点+删除

当需要讨论节点个数为1 、 2时,思考能否用虚拟头节点简化。

与上题综合可得出结论:

  • 当找n/2上取整的中点时,快慢指针从head开始循环,条件为while(fast && fast->next)

  • 当找n/2下取整的中点时,快慢指针从dummyHead开始循环,条件为while( fast->next && fast->next->next)

    此时slow->next为待找点

class Solution {
public:
    ListNode* deleteMiddle(ListNode* head) {
        ListNode* fast = head;
        ListNode* slow = head;
        if(!head->next) return NULL;
        if(!head->next->next) {
          head->next = nullptr;
          return head;
          }
        while(fast->next->next && fast->next && fast->next->next->next){
          fast = fast->next->next;
          slow = slow->next;
        }
        ListNode* tmp = slow->next;
        slow->next = tmp->next;
        return head;
    }
};

运用dummyHead简化讨论情况

class Solution {
public:
    ListNode* deleteMiddle(ListNode* head) {
        ListNode* dummyHead = new ListNode(0 , head);
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        while(fast->next && fast->next->next){
          fast = fast->next->next;
          slow = slow->next;
        }
        ListNode* tmp = slow->next;//slow的下一个节点为待删除节点
        slow->next = tmp->next;
        return dummyHead->next;
    }
};

234. 回文链表

思路:快慢指针找中点 + 翻转后半段,然后比较,相当于两头开始比较。

回文的定义是收尾相等,往中间收缩。

class Solution {
public:
    bool isPalindrome(ListNode* head){
      //快慢指针找中点(下取整)
        ListNode* dummyHead = new ListNode(0 , head);
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        while(fast->next && fast->next->next){
          fast  = fast->next->next;
          slow = slow->next;
        }
      //从slow->next开始翻转后半段
        ListNode* pre = nullptr;
        ListNode* cur = slow->next;
        ListNode* tmp;
        while(cur){
          tmp = cur->next;
          cur->next = pre;
          pre = cur;
          cur = tmp;
        }
      //cur1从头开始,pre是后半段的头开始。while里其实可以写while(pre),因为前一段的slow->next并没有被砍断,而是指向翻转后的链表尾处。
       ListNode* cur1 = head;
        while(cur1 && pre){
        if(cur1->val != pre->val)     return false;   
          cur1 = cur1->next;
          pre = pre->next;                 
        }
        return true;
    }
};

2130. 链表最大孪生和

思路:上一题回文链表的基础上每次更新最大值res

class Solution {
public:
    int pairSum(ListNode* head) {
        ListNode* dummyHead = new ListNode(0 , head);      
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        while(fast->next && fast->next->next){
          fast = fast->next->next;
          slow = slow->next;
        }
        ListNode* pre = nullptr;
        ListNode* cur = slow->next;
        while(cur){
          ListNode* tmp = cur->next;
          cur->next = pre;
          pre = cur;
          cur = tmp;
        }
        ListNode* cur1 = head;
        int res = 0;
        while(pre){
          int val = cur1->val + pre->val;
          res = max(res , val);
          pre = pre->next;
          cur1 = cur1->next;
        }
        return res;
    }
};

143. 重排链表

思路:也是回文链表的系列题

依旧翻转后半段链表,把slow->next处断开,这样前半段的长度小于等于后半段,while条件为前半段非空(cur1->next)。依次把pre节点插入cur1cur1->next之间,然后循环往后移。当前半段遍历完时,把后面剩余的接到cur1后(cur1 ->next = pre)。

class Solution {
public:
    void reorderList(ListNode* head){
        if(!head->next || !head->next->next)  return;
        ListNode* dummyHead = new ListNode(0 , head);      
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        while(fast->next && fast->next->next){
          fast = fast->next->next;
          slow = slow->next;
        }
        ListNode* pre = nullptr;
        ListNode* cur = slow->next;
        while(cur){
          ListNode* tmp = cur->next;
          cur->next = pre;
          pre = cur;
          cur = tmp;
        }
        slow->next = nullptr;
        ListNode* cur1 = head;
        while(cur1->next){
          ListNode* nxt = cur1->next;
          ListNode* nxt1 = pre->next;
          cur1->next = pre;
          pre->next = nxt;
          pre = nxt1;
          cur1 = nxt;
        }
        cur1->next = pre;       
    }  
};

141. 环形链表

思路:快慢指针

判断有无环,能碰到则有环,while条件是(fast && fast->next),如果没环循环会停止。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* dummyHead = new ListNode(0 , head);
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        while(fast && fast->next){
          fast = fast->next->next;
          slow = slow->next;
          if(fast == slow) return true;
        }
        return false;
    }
};

142. 环形链表 II

思路:快慢指针+简单的推导

如果有环那么slow与fast从head开始遍历,相遇的节点记为slow,此时再令cur1从头开始遍历,cur1与slow相遇的位置即为环的入口。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {      
        if(!head || !head->next)  return NULL; 
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast && fast->next){
          fast = fast->next->next;
          slow = slow->next;
          if(fast == slow) {
            ListNode* cur = head;
            while(cur != slow){
              cur = cur->next;
              slow = slow->next;
            }
            return cur;
          }
        }
       return NULL;
    }
};

457. 环形数组是否存在循环

思路:模拟

根据题意,我们可以从每个下标 i 进行出发检查,如果以某个下标 i 为出发点发现了「循环」,返回 True,否则返回 False。

唯一需要注意的细节是,当我们处理到的下标为 cur,计算下一个跳转点 next=cur+nums[cur] 时,对于越过数组的情况进行处理:

  • 如果 next 为负数:在 next 的基础上增加 n∗⌈next/n⌉,将其映射回正值;

  • 如果 next 为正数:将 next 模数组长度 n,确保不会越界。

整理一下,我们可以统一写成 next = ((cur + nums[cur]) % n + n ) % n

在 check 内部,当以下任一条件出现,则可以结束检查(令 k 为记录过程中扫描过的下标数量):

  1. 如果在检查过程中,找到了与起点相同的下标,且 k>1,说明存在符合条件的「循环」,返回 True;

  2. 如果检查过程中扫描的数量 k 超过了数组长度 n,那么根据「鸽笼原理」,必然有数被重复处理了,同时条件一并不符合,因此再处理下去,也不会到达与起点相同的下标,返回 False;

  3. 处理过程中发现不全是正数或者负数,返回 False。

拿样例图举例:img

举例:当i = 0时,cur = 0 , tag = offest + 0。在while循环里会一直替换cur = 2 、3。此时nums[next] == nums[0] == tag,外面的tag一直是0的tag没换,因此能返回true。

class Solution {
    const int OFFSET = 10010;
public:
    bool circularArrayLoop(vector<int>& nums) {
        int n = nums.size();
        for(int i = 0; i < n; i++) {
                int cur = i, tag = OFFSET + i;
                //排除非循环节点
                while(nums[cur] < OFFSET) {  
                    int next =  ((cur + nums[cur]) % n + n) % n;
                    //退出条件1: 自我循环
                    if(next == cur) break;
                    //发现next已经访问过,存在循环,直接返回true
                    if(nums[next] == tag) return true;
                    //退出条件2:非全正或全负
                    if(nums[cur] * nums[next] < 0) break; 
                    //更新
                    nums[cur] = tag;
                    cur = next;
                }          
        }
        return false;
    }
};

posted @ 2024-11-22 22:18  七龙猪  阅读(1)  评论(0)    收藏  举报
-->