算法专题一: 双指针

目录

  • 前言
  • [1. 移动零(easy)](#1. 移动零(easy))
  • [2. 复写零(easy)](#2. 复写零(easy))
  • [3. 快乐数(medium)](#3. 快乐数(medium))
  • [4. 盛水最多的容器(medium)](#4. 盛水最多的容器(medium))
  • [5. 有效三角形的个数(medium)](#5. 有效三角形的个数(medium))
  • [6. 和为 s 的两个数字(easy)](#6. 和为 s 的两个数字(easy))
  • [7. 三数之和(medium)](#7. 三数之和(medium))
  • [8. 四数之和(medium)](#8. 四数之和(medium))

前言

常见的双指针有两种形式,一种是对撞指针,一种是左右指针。

1. 对撞指针: ⼀般用于顺序结构中,也称左右指针。

  • 对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。
  • 对撞指针的终止条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
    ◦ left == right (两个指针指向同⼀个位置)
    ◦ left > right (两个指针错开)

2. 快慢指针:又称为龟兔赛跑算法,其基本思想就是使用两个移动速度不同的指针在数组或链表等序列结构上移动。

这种方法对于处理环形链表或数组非常有用。

其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使用快慢指针的思想。

快慢指针的实现方式有很多种,最常用的⼀种就是:

• 在一次循环中,每次让慢的指针向后移动⼀位,而快的指针往后移动两位,实现一快一慢。

1. 移动零(easy)

题目链接:移动零

思想

在本题中,我们可以用一个 cur 指针来扫描整个数组,另一个 dest 指针用来记录非零数序列的最后一个位置。根据 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。

cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        for(int cur = 0, dest = -1;cur<nums.size();++cur)
        {
            if(nums[cur]!=0)
            {
                ++dest;
                swap(nums[dest],nums[cur]);
            }
        }
    }
};

2. 复写零(easy)

题目链接: 复写零


算法思路

如果从前向后进行原地复写操作的话,由于0的出现会复写两次,导致没有复写的数被覆盖掉,因此我们选择从后往前的复写策略,但是从后往前复写的时候,我们需要找到最后一个复写的数,因此我们的大体流程分为两步:

  1. 先找到最后一个复写的数;
  2. 然后从后往前进行复写操作.
cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int cur = 0, dest = -1;

        while(cur < arr.size())
        {
            if(arr[cur])
                ++dest;
            else
                dest+=2;
            if(dest >= arr.size()-1)
                break;
            ++cur;
        }
        if(dest == arr.size())
        {
            dest--;
            arr[dest--] = 0;
            cur--;
        }
        while(cur>=0)
        {
            if(arr[cur])
            {
                arr[dest--] = arr[cur--];
            }else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }

        }

    }
};

3. 快乐数(medium)

题目链接: 快乐数


算法思路

根据分析我们可以知道, 当重复执行x的时候, 数据就会陷入到一个循环中, 而快慢指针有一个特性,就是在一个圆圈中, 快指针总是会追上慢指针的,也就是说他们会相遇在一个位置上, 如果相遇位置的值是1, 那么这个数一定是快乐数, 如果相遇的位置不是1的话, 那么这个数就不是快乐数.

cpp 复制代码
class Solution {
public:
    int mutify(int n)
    {
        int ret =  0;
        while(n)
        {
            int ge = n % 10;
            ret += ge*ge;
            n/=10;
        }
        return ret;
    }
    bool isHappy(int n) {
        int fast = mutify(n), slow = n;
        while(fast != slow)
        {
            fast = mutify(mutify(fast));
            slow = mutify(slow);
        }
        return fast==1;
    }
};

4. 盛水最多的容器(medium)

题目链接: 盛水最多的容器

算法思路

设置两个指针left,right分别指向容器的左右两个端点,此时容器的容积v = (right - left) * min(hegiht[right] , height[left]). 容器的左边界为height[left], 右边界为height[right].如果此时我们固定一个边界, 改变另一个边界,水的容积会有如下变化形式:

  1. 容器的宽度一定变小.
  2. 由于左边界较小, 决定了水的高度,如果改变左边界,新的水面高度不确定,但是一定不会超过右边的柱子高度,因此容器的容积可能会增大.
  3. 如果改变右边界,无论右边界移动到哪里,新的水面的高度一定不会超过左边界,也就是不会超过现在的水面高度,但是由于容器的宽度减小,因此容器的容积一定会变小.

由此我们可以left++跳过,舍弃左边界和它的组合情况,继续下一个判断, 不断重复, 就可以舍去大量不必要的枚举过程,直到left和right相遇.

cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0, right = height.size()-1, ret = 0;
        while(left < right)
        {
            int v = min(height[left],height[right]) * (right - left);
            ret = max(v,ret);
            if(height[left]<height[right])
                left++;
            else right--;
        }
        return ret;
    }
};

5. 有效三角形的个数(medium)

题目链接: 有效的三角形个数


算法思路

cpp 复制代码
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int ret = 0;
        for(int i = nums.size()-1;i>=2;i--)
        {
            int left = 0,right = i-1;
            while(left<right)
            {
                if(nums[left]+nums[right] <= nums[i])
                {
                    left++;
                }
                else
                {
                    ret+=right - left;
                    right--;
                }
            }  
        }
        return ret;
    }
};

6. 和为 s 的两个数字(easy)

题目链接: 查找总价格为目标值的两个商品

算法思路

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        int left = 0, right = price.size()-1;
        while(left < right)
        {
            if(price[left]+price[right] > target)
            {
                right--;
            }
            else if(price[left]+price[right]<target)
            {
                left++;
            }
            else
            {
                return {price[left],price[right]};
            }
        }
        return {-1,-1};
    }
};

7. 三数之和(medium)

题目链接: 三数之和


算法思路

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> vv;
       int n = nums.size();
       for(int i = 0; i<n;)
       {
            int target = -nums[i];
            int left = i+1,right = n-1;
            while(left<right)
            {
                int sum = nums[left]+nums[right];
                if(sum < target) left++;
                else if(sum > target) right--;
                else  
                {
                    vv.push_back({nums[left],nums[right],nums[i]});
                    left++,right--;
                    while(left<right&&nums[left]==nums[left-1]) left++;
                    while(left<right&&nums[right]==nums[right+1]) right--;
                }
            }
            i++;
            while(i<n&&nums[i]==nums[i-1]) i++;
       }
       return vv; 
    }
};

8. 四数之和(medium)

题目链接: 四数之和

算法思路

csharp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        vector<vector<int>> vv;
        int n = nums.size();
        for(int i = 0; i < n ;)
        {
            for(int j = i+1;j<n;)
            {
                long long a = (long long)target - nums[i] - nums[j];
                int left = j+1,right =n-1;
                while(left<right)
                {
                    int sum = nums[left] + nums[right];
                    if(sum<a) left++;
                    else if(sum>a) right--;
                    else
                    {
                        vv.push_back({nums[left],nums[right],nums[i],nums[j]});
                        left++,right--;
                        //去重1
                        while(left<right && nums[left] == nums[left-1]) left++;
                        while(left<right && nums[right] == nums[right+1]) right--;
                    }
                }
            j++;
            //去重2
            while(j<n && nums[j] == nums[j-1]) j++;
            }
        i++;
        while(i<n&&nums[i] == nums[i-1]) i++;    
        }
          return vv;
    }
  
};

相关推荐
迷迭所归处14 分钟前
C++ —— 关于vector
开发语言·c++·算法
leon62543 分钟前
优化算法(一)—遗传算法(Genetic Algorithm)附MATLAB程序
开发语言·算法·matlab
CV工程师小林44 分钟前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
Navigator_Z1 小时前
数据结构C //线性表(链表)ADT结构及相关函数
c语言·数据结构·算法·链表
还听珊瑚海吗1 小时前
数据结构—栈和队列
数据结构
Aic山鱼1 小时前
【如何高效学习数据结构:构建编程的坚实基石】
数据结构·学习·算法
white__ice2 小时前
2024.9.19
c++
天玑y2 小时前
算法设计与分析(背包问题
c++·经验分享·笔记·学习·算法·leetcode·蓝桥杯
姜太公钓鲸2332 小时前
c++ static(详解)
开发语言·c++
菜菜想进步2 小时前
内存管理(C++版)
c语言·开发语言·c++