算法专题一: 双指针

目录

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

相关推荐
Reese_Cool几秒前
【C++】从C语言到C++学习指南
c语言·c++·1024程序员节
pursuit_csdn几秒前
力扣 238. 除自身以外数组的乘积
数据结构·算法·leetcode
小柯J桑_2 分钟前
C++:探索AVL树旋转的奥秘
开发语言·c++·avl树
数学人学c语言3 分钟前
从熟练Python到入门学习C++(record 6)
c++·python·学习
黑眼圈的小熊猫7 分钟前
数据结构--跳表
数据结构
深情汤姆1 小时前
C++ 红黑树
数据结构·c++
skaiuijing2 小时前
Sparrow系列拓展篇:消息队列和互斥锁等IPC机制的设计
c语言·开发语言·算法·操作系统·arm
绵绵细雨中的乡音3 小时前
C++第28课-布隆过滤器的介绍
c++·哈希算法
C++忠实粉丝3 小时前
计算机网络socket编程(5)_TCP网络编程实现echo_server
网络·c++·网络协议·tcp/ip·计算机网络·算法
kim56594 小时前
excel版数独游戏(已完成)
算法·游戏·excel·数独