leetcode_刷题总结(c++)_快慢指针_环形链表

主要参考:

问题:
若链表存在环,请找到入环点。

快慢指针算法思想

定义快慢指针fast和slow,起始均位于链表头部。
规定fast每次后移2步,slow后移1步
(1)若fast遇到null节点,则表示链表无环,结束;
(2)若fast和slow再次相遇,则表示链表中有环
当fast和slow相遇时,额外创建指针ptr,并指向链表头部,且每次后移1步,最终slow和ptr会在入环点相遇。

问题1、为什么fast和slow一定会相遇?

例如,两人绕操场跑圈,只要一直跑,快的肯定会追上慢的,即再次相遇。

可以把操场想成一个个格子组成的链表环,slow每次跳一格,fast每次跳两格,只有两人站在同一个格子上,才算相遇。如果是跳跃式的穿过,那是不算相遇的。
slow和fast之间的距离,有以下几种情况:

  • 相距1格,下次相遇。
  • 相距2格,下一次移动,变为相距1格。
  • 其它时刻,每次环内移动,距离缩短1,直至相距2格。

通过分析可知,快慢指针一定会相遇。

问题2、fast和slow相遇时,slow指针是否绕环超过一圈?

环长为N,还是slow进环的时候,相距为n。那么一定n<N。
根据第一题可知,slow每次 前进后 距离变为n-1,当距离为0的时候也就是n-1*n。slow就走了n。
因为n<N,所以一定不超过1圈。

问题3、为什么ptr和slow相遇的节点一定是入环点?

在这里插入图片描述
如图所示,假设环外部分长度为a,slow指针进入环后,又走了b的距离与fast相遇。

此时,fast指针已经走完了环的n圈,因此它走过的总距离为: a+n(b+c)+b=a+(n+1)b+nc
通过问题2的解答,slow指针是不可能绕环超过一圈的,即相遇时,slow走的距离为a+b

任意时刻,fast指针走过的距离都为slow指针的2倍 =》因此得出关系式:

a+(n+1)b +nc = 2(a+b) =》a = c+(n - 1)(b+c)

a=c+(n−1)(b+c)这个等量关系特别重要,其中c表示slow与fast指针相遇位置到入环点的距离,而(n−1)(b+c)则是 n-1圈的环长。

因此,只需要再添加一个指针ptr指向头结点,当它走完环外距离a的时候,则会与在绕圈等它的slow相遇。而相遇点恰好是入环点

问题4、为什么fast指针每次移动2步,能不能移动3、4、5…步?

设环外长度为w,环长度为s。取一特殊值j,保证j>w且是s整数倍的最小值。将slow走了j步后的位置记为X(j),则fast走了kj步,记为X(kj),其中k为fast与slow的速度比值。

因为j>w,所以slow和fast都在环内,而且X(kj)可以看做从X(j)出发,走了(k-1)*j步,因为j是环长的整数倍,所以又回到了X(j),两者相遇。

从上面的分析可知,无论fast取任何值,两者都会相遇。即使比值K是小数2.3(如slow=10,fast=23),也只需要j乘以10,就证明了这个问题。

之所以取fast=2,是因为快指针的时间复杂度为O(n*fast),可以保证算法效率最高。

快慢指针解题模板

 public class Solution {
     public ListNode detectCycle(ListNode head) {
         if (head == null) {
             return null;
         }
         ListNode slow = head, fast = head;
         while (fast != null) {
             slow = slow.next;
             if (fast.next != null) {
                 fast = fast.next.next;
             } else {
                 return null;
             }
             if (fast == slow) {
                 ListNode ptr = head;
                 while (ptr != slow) {
                     ptr = ptr.next;
                     slow = slow.next;
                 }
                 return ptr;
             }
         }
         return null;
     }
 }

leetcode部分相关题目

链表:

leetcode中链表的声明

/**
 * 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) {}
 * };
 */

141. 环形链表

141. 环形链表

在这里插入图片描述

class Solution {
public:
    bool hasCycle(ListNode *head) {
        //快慢指针判断链表中是否有环

        //边界考虑 只有1个或没有节点 不可能为环形链表
        if(head==NULL || head->next==NULL){
            return false;
        }
        ListNode * slow=head;
        ListNode * fast=head;
        while(fast){
            if(fast->next==NULL){
                 return false;
            }
            slow=slow->next;
            fast=fast->next->next;
            if(slow==fast){
                break;
            }
        }
        if(fast==slow){
            return true;
        }
        else{
            return false;
        }
    }
};

142. 环形链表 II

142. 环形链表 II
快慢指针:
如果存在有环,那么快慢指针必然相互追上,但是追上的位置不一定是环 开始的位置。
因此需要考虑 在判断链表有环后怎么消除两个指针之间的距离差
(参考前面问题3:为什么ptr和slow相遇的节点一定是入环点)

class Solution {
public:
    //快慢指针
    ListNode *detectCycle2(ListNode *head) { 
        if(head==NULL || head->next==NULL){
            return NULL;
        }
        ListNode *slow=head;
        ListNode *fast=head;
        //第一轮追及 相等时退出
        while(fast){
            if(fast->next==NULL){
                return NULL;
            }
            slow=slow->next;
            fast=fast->next->next;
            if(slow==fast){
                break;
            }
        }
        //第二轮追及
        if(slow!=fast){
            return NULL;
        }
        else{
            ListNode *pre1=head;
            ListNode *pre2=slow;
            while(pre1!=pre2){
                pre1=pre1->next;
                pre2=pre2->next;
            }
            return pre1;
        }
    }
  
};

哈希表:

class Solution {
public:
     //哈希表
    ListNode *detectCycle(ListNode *head) {
        //将链表中每个节点的位置存放在哈希表中
        unordered_set<ListNode *> hashmap;
        ListNode *p=head;
        while(p){
            if(hashmap.count(p)==0){
                hashmap.insert(p);
                p=p->next;
            }
            if(hashmap.count(p)!=0){
                return p;
            }
        }
        return NULL;
    }
};

234. 回文链表

234. 回文链表

class Solution {
public:
    //反转链表
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr != nullptr) {
            ListNode* nextTemp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    bool isPalindrome(ListNode* head) {
        //快慢指针+反转链表
        //利用快慢指针找到链表中间位置
        //边界
        if(head==NULL){
            return false;
        }
        if(head->next==NULL){
            return true;
        }
        ListNode *slow=head;
        ListNode *fast=head;
        while(fast!= nullptr){
            //走到最后一个有效位置
            if(fast->next == nullptr||fast->next->next == nullptr)
                break;
            slow=slow->next;
            fast=fast->next->next;
        }

        //最后slow位置即为链表中间位置
        //但是 如果直接这样还要考虑奇数偶数问题
        ListNode* p1 = head;
        ListNode* p2 = reverseList(slow->next);
        bool result = true;
        while (result && p2 != nullptr) {
            if (p1->val != p2->val) {
                result = false;
            }
            p1 = p1->next;
            p2 = p2->next;
        }
        return result;
    }
};

数组:

287. 寻找重复数

287. 寻找重复数

在这里插入图片描述

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        //快慢指针
        //因为题目给定 只有一个重复的整数
        int slow=0;
        int fast=0;
        while(nums[fast]!=NULL){
            slow=nums[slow];
            fast=nums[nums[fast]];
            if(slow==fast)
                break;
        }
        int pre1=0;
        int pre2=slow;
        while(pre1!=pre2){
            pre1=nums[pre1];
            pre2=nums[pre2];
        }
        return pre1;
    }
};

202. 快乐数

202. 快乐数
在这里插入图片描述
思路(参考官方题解):
7是快乐数
在这里插入图片描述
116不是快乐数
在这里插入图片描述
问题转换为是否存在循环

(1)可以用哈希表检测循环

class Solution {
public:
    int nextNum(int n){
        int cur;
        while(n>0){
            int d=n%10;
            cout<<d<<endl;
            n=n/10;
            cur=cur+d*d;
        }
        return cur;
    }
    bool isHappy(int n) {
        unordered_map<int,int> mp;
        while(n!=1 && mp.count(n)==0){
            mp[n]++;
            n=nextNum(n);

        }
        cout<<n;
        return n==1;
    }
};

(2)可以用快慢指针检测循环

class Solution {
public:
    int nextNum(int n){
        int cur;
        while(n>0){
            int d=n%10;
            cout<<d<<endl;
            n=n/10;
            cur=cur+d*d;
        }
        return cur;
    }
    bool isHappy(int n) {
        int fast=n;
        int slow=n;
        slow=nextNum(slow);//slow走一步
        fast=nextNum(nextNum(fast));//fast走两步
        while(fast!=1){
            if(fast==slow)
                break;
            slow=nextNum(slow);//slow走一步
            fast=nextNum(nextNum(fast));//fast走两步

        }
        if(fast==1)
            return true;
        else
            return false;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值