算法专题一: 双指针

目录

  • 前言
  • [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;
    }
  
};

相关推荐
shymoy4 分钟前
Radix Sorts
数据结构·算法·排序算法
风影小子13 分钟前
注册登录学生管理系统小项目
算法
黑龙江亿林等保15 分钟前
深入探索哈尔滨二级等保下的负载均衡SLB及其核心算法
运维·算法·负载均衡
起名字真南17 分钟前
【OJ题解】C++实现字符串大数相乘:无BigInteger库的字符串乘积解决方案
开发语言·c++·leetcode
少年负剑去18 分钟前
第十五届蓝桥杯C/C++B组题解——数字接龙
c语言·c++·蓝桥杯
lucy1530275107918 分钟前
【青牛科技】GC5931:工业风扇驱动芯片的卓越替代者
人工智能·科技·单片机·嵌入式硬件·算法·机器学习
cleveryuoyuo18 分钟前
AVL树的旋转
c++
杜杜的man34 分钟前
【go从零单排】迭代器(Iterators)
开发语言·算法·golang
神仙别闹41 分钟前
基于MFC实现的赛车游戏
c++·游戏·mfc
小c君tt1 小时前
MFC中 error C2440错误分析及解决方法
c++·mfc