LeetCode 快慢指针 双指针法阶段性总结 个人向


双指针法通常被用于 简化多层循环遍历的场景,降低时间复杂度(通常降低一个次幂)。
双指针并不是固定的公式,而是一种思维的方式~

链表 双指针法 快慢指针

真题 LeetCode141 环形链表

题目链接
在这里插入图片描述
解题思路:
快指针每次移动两步,慢指针每次一步。如果链表存在环,则快指针先一步进入环然后循环。一旦慢指针到了环的入口处,问题就变成了一个追击问题,此时快指针离入口处的距离就变成了的待追击的长度(假设长度为x)。每移动一次,快指针离慢指针的距离就缩小1步 (2步减1步),那么再经过x次移动后,两个指针必能相遇。
AC代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        //双指针法
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast!=NULL && fast->next!=NULL){
            fast = fast->next->next;
            slow = slow->next;
            if(fast==slow) return true;
        }
        return false;
    }
};

真题 LeetCode142 环形链表II. 环有了,那入口节点呢?

题目链接
在这里插入图片描述
AC代码:
解题思路:
在这里插入图片描述
若快慢指针在上图的相遇点处相遇,假设此时快指针已经绕环nf圈,慢指针绕环ns圈,则有:

len_slow = a + (b+c)*ns + b
len_fast = a + (b+c)*nf + b
len_fast = 2*len_slow

结合可得:b+a = (b+c)*(nf-2*ns). (b+c)即为环的长度,故而b+a是环的长度的整数倍。也就是说,当slow指针从相遇处再移动a个长度时,正好处在环的起始位置
因此,若有一个指针index从链表头节点同时与slow指针出发,每次移动一个单位长度,则经过a次移动后,index第一次到达环的入口处,也即第一次与slow指针相遇。
其实,上面的等式还可以进一步简化:当slow节点第一次到达环的入口时,fast指针与slow指针变成了追击关系,此时两者之间的距离为[0, b+c). 包含0是因为二者可能在入口处就相遇了。接下来每移动一次,二者距离减一,最多不会超过环的长度(b+c)就已经追上来了,也就是说两指针第一次相遇时慢指针绕环0圈ns为0,等式可简化为b+a = (b+c)*nf.
当nf为1时,a与c相等,相较于环(b+c)来说a较短,如上图;当nf至少为2时,a的长度相较于环长来说很大,如下图:
在这里插入图片描述

AC代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast && fast->next){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow){
                slow = head;
                while(slow!=fast){
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;
            }
        }
        return NULL;
    }
};

推荐阅读

链表阶段性总结

数组

LeetCode 15 三数之和

题目链接
在这里插入图片描述
解题思路:该题目需要快速判断满足某个要求的整数或整数对存在不存在,例如已知c求满足a+b+c=0的整数对<a,c>是否存在. 首先想到哈希法,但题目要求了需要去掉重复的元组,纵使使用unordered_set,去重仍然是一个比较棘手的问题。
如何有效去重?
想一想怎么样才能使相等的元素紧紧相邻呢?
排序!
想到使用排序来简化降重之后,此题的解法就比较清晰了。无论是哈希法还是双指针遍历都可以,下面介绍双指针遍历的解法:排序+遍历*双指针/前后指针遍历,要注意两处需要去重的地方
AC代码:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());//排序
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for(int i=0; i< nums.size(); i++){//遍历
            //a去重. 假设排序后的nums[0]为-1,且总共有m个-1,也即nums[0]=nums[1]=...=nums[m-1]= -1
            //那么在i=0的遍历时,已经将最多m个-1存在的情况考虑完了
            //当i=1时,最多只有m-1个-1, 已经在上一轮遍历中考虑过了,因此需要去重
            //感觉不好理解的话,把三元组扩充成n元组,看看是不是能容易想明白
            if(i>0 && nums[i] == nums[i-1]) continue;
            // 错误去重方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            int left = i+1, right = nums.size()-1, tmp;
            while(left<right){//前后指针遍历
                tmp= nums[i] + nums[left] + nums[right];
                if(tmp>0) right--;
                else if(tmp<0) left++;
                else{
                    res.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    left++; right--;
                    //b和c降重  因为是三元组,在a相同的情况下若b或c相同,则最终满足要求的三元组一定已经重复了
                    while(left<right && nums[left] == nums[left-1]){left++;}
                    while(left<right && nums[right] == nums[right+1]){right--;}
                }
            }
        }
        return res;
    }
};

LeetCode 16 最接近的三数之和

题目链接
在这里插入图片描述
解题思路:仍然是 排序+遍历*前后指针遍历 来避免三重循环枚举
AC代码:

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        //由提示知3 <= nums.length <= 1000
        int res=nums[0]+nums[1]+nums[2];
        for(int i=0;i<nums.size();i++){
            if(i>0 && nums[i] == nums[i-1]) continue;
            int left = i+1, right = nums.size() - 1, tmp;
            while(left<right){
                tmp = nums[i] + nums[left] + nums[right];
                if(abs(tmp-target) < abs(res-target)) res=tmp;
                if(tmp>target){
                    right--;
                    //避免重复枚举
                    while(left<right && nums[right] == nums[right+1]){right--;}
                }
                else if(tmp<target){
                    left++;
                    while(left<right && nums[left] == nums[left-1]){left++;}
                }
                else return target;    
            }
        }
        return res;
    }
};

LeetCode 18 四数之和

四数之和
在这里插入图片描述
解题思路跟三数之和非常相似,多了一层嵌套循环,然后target不再固定为零.
AC代码:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        if(nums.size()<4) return res;
        sort(nums.begin(), nums.end());
        for(int i=0;i<nums.size();i++){
            if(i>0 && nums[i] == nums[i-1]) continue; //a去重
            for(int j=i+1;j<nums.size();j++){
                if(j>i+1 && nums[j] == nums[j-1]) continue; //b去重
                int left = j+1, right = nums.size() - 1;
                while(left<right){
                	// nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
                    if(nums[i] + nums[j]>target-(nums[left] + nums[right])) right--;
                    else if(nums[i] + nums[j]<target-(nums[left] + nums[right])) left++;
                    else{
                        res.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
                        left++; right--;
                        //c和d去重
                        while(left<right && nums[left] == nums[left-1]){left++;}
                        while(left<right && nums[right] == nums[right+1]){right--;}
                    }
                }
            }
        }
        return res;
    }
};

小总结

三数之和: 排序+遍历*双指针解法 是一层for循环num[i]将a固定,然后循环内有left和right下标作为前后指针,找到nums[i] + nums[left] + nums[right] == 0, 需要注意两处去重的地方。

四数之和:排序+遍历*遍历*双指针解法是两层for循环nums[k] + nums[i]将a+b固定,依然是循环内有left和right下标作为前后指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O( n^2 ),四数之和的时间复杂度是O( n^3 ), 需要注意三处去重的地方。

同理,五数之和、六数之和类推都采用这种解法。

对于三数之和,双指针法就是将原本暴力O( n^3 )的解法,降为O( n^2 )的解法;四数之和的双指针解法就是将原本暴力O( n^4 )的解法,降为O( n^3 )的解法。

练习题目 LeetCode 27 移除元素

题目链接
在这里插入图片描述
AC代码:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        //避免多次遍历  考虑使用双指针
        int slow=0, fast=0;
        while(fast!=nums.size()){
            if(nums[fast]==val){
                fast += 1;
            }
            else{
                nums[slow] = nums[fast];
                fast += 1;
                slow += 1;
            }
        }
        return slow;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值