算法——双指针

本文详细介绍了双指针算法的概念、常见形式,以及在LeetCode上的八个具体应用实例,包括移动零、复写零、快乐数等问题的解决方案。这些实例展示了双指针算法在数组、链表和整数处理中的高效运用。
摘要由CSDN通过智能技术生成

目录

1. 什么是双指针算法?

2. 双指针算法的常见形式

3. 应用实例

1. 移动零

2. 复写零

3. 快乐数

4. 盛多最水的容器

5. 有效三角形的个数

6. 和为s的两个数

7. 三数之和

8. 四数之和


1. 什么是双指针算法?

双指针算法是一种常用于解决数组或链表中的问题的技巧。它涉及使用两个指针(索引或引用),通常分别称为“快指针”和“慢指针”或“左指针”和“右指针”,以协同进行遍历或搜索。

该算法的核心思想是通过移动这两个指针来实现特定的目标,例如寻找一对元素的和、判断是否存在某种关系或在特定条件下移动其中一个指针。双指针算法通常能够在O(n)的时间复杂度内解决问题,具有较好的效率。 

2. 双指针算法的常见形式

常见的双指针算法有以下几种类型:

  1. 对撞指针: 在数组两端分别设立左右指针,通过向中间移动这两个指针来解决问题。例如,寻找数组中的两个元素,它们的和等于给定值。

  2. 快慢指针: 一个指针移动速度较快,另一个移动速度较慢。这常用于解决链表中的问题,如判断链表是否有环,找到链表的中间节点等。

  3. 滑动窗口(有专题描述:算法——滑动窗口): 使用两个指针维护一个窗口,通过移动窗口的左右边界解决问题。这类问题常见于字符串和数组处理,例如找到最短的包含所有字符的子串。

  4. 同向双指针: 两个指针方向相同,通过控制其中一个指针的位置来处理问题。例如,在一个有序数组中查找两个数,使它们的和等于给定值。

3. 应用实例

1. 移动零

题目链接:283. 移动零 - 力扣(LeetCode)

解析:看到这道题我们这样解决,即定一个左端点在第一个0处,右指针向右寻找第一个非0元素,找到后交换这两个元素,直到遍历整个数组,代码如下

class Solution 
{
public:
    void moveZeroes(vector<int>& nums) 
    {
        for (int left = 0, right = 0; right < nums.size(); right++)
            if (nums[right] != 0) 
                swap(nums[left++], nums[right]);
    }
};

2. 复写零

题目链接:1089. 复写零 - 力扣(LeetCode)

解析:对于这道题,我们分析一下可以发现,要想在原数组上做到复写0,我们需要从右向左复写,不然会覆盖到后面的数字,那么我们如何找到最后一个修改后数组的位置呢?那么在这里我们可以定义一个快指针dest表示慢指针所指元素是否到达数组结尾,定义一个慢指针cur表示修改后数组中末尾元素,代码如下

class Solution 
{
public:
    void duplicateZeros(vector<int>& arr) 
    {
        int n = arr.size();
        int cur = 0;
        int dest = -1;

        while (dest < n-1)
        {
            // 若cur对应元素为0则表示需要复写,dest+=2
            if (arr[cur] != 0)
            {
                dest++;
            }
            else
            {
                dest += 2;
            }
            if (dest < n-1)
                cur++;
        }

        // 从右向左开始复写
        while (cur >= 0)
        {
            // 若dest超出数组范围,预处理末尾元素即可
            if (dest > n-1)
            {
                arr[n-1] = 0;
                dest-=2;
                cur--;
                continue;
            }

            // 非0元素复写一次,0元素复写2次
            if (arr[cur] != 0)
            {
                arr[dest] = arr[cur];
                dest--;
                cur--;
            }
            else
            {
                arr[dest] = 0;
                arr[dest-1] = 0;
                dest -= 2;
                cur--;
            }
        }
    }
};

3. 快乐数

题目链接:202. 快乐数 - 力扣(LeetCode)

解析:我们首先分析一下19是如何变为1的,即

随后我们再来分析一下2是为何变成不了1的,即

可以看到,最后整个生成快乐数的过程形成了一个环,因此我们可以使用快慢指针判断链表是否有环的思想来解决这道题(参考:OR36链表的回文结构),代码如下

class Solution {
public:
    int transform(int n)
    {
        int ret = 0;
        while (n)
        {
            int num = (n % 10) * (n % 10);
            ret += num;

            n /= 10;
        }

        return ret;
    }

    bool isHappy(int n) 
    {
        int slow = transform(n);
        int fast = transform(transform(n));

        while (slow != fast)
        {
            slow = transform(slow);        
            fast = transform(transform(fast));
        }

        if (fast == 1) return true;
        else return false;
    }
};

对于这道题来说,我们应该还有一个疑问,为什么不会到达1的时候一定会形成一个环呢?因为int类型最大值为为‭‭2 147 483 647‬‬, 所以平方和最大的数是1 999 999 999,平方和为1 + 81*9 = 724。任何数的平方和都在1到724之间,724次循环之内一定有重复的,即一定会进入循环区间,从而形成环。

4. 盛多最水的容器

题目链接:11. 盛最多水的容器 - 力扣(LeetCode)

解析:分析一下题意我们可以发现,我们可以定义左右指针,分别从两端向中间遍历,哪一端低就像反方向移动一位(若左边低则left++,右边低则right--),每一次将本次计算出的面积与上一次相比较,直到遍历到最后得到最终结果,代码如下

class Solution 
{
public:
    int maxArea(vector<int>& h) 
    {
        int left = 0;
        int right = h.size()-1;
        int max = 0;
        while (left < right)
        {
            if (h[left] <= h[right])
            {
                int tmp = h[left] * (right - left);
                if (tmp > max) max = tmp;

                left++;
            }
            else
            {
                int tmp = h[right] * (right - left);
                if (tmp > max) max = tmp;

                right--;
            }
        }
        
        return max;
    }
};

5. 有效三角形的个数

题目链接:611. 有效三角形的个数 - 力扣(LeetCode)

解析:要想判断三个数是否能组成三角形,我们知道需要满足以下条件:

1. 任意两边之和大于第三边

2. 任意两边之差小于第三边

那么我们先将数组排序,可以固定一条边(此边最小),将这个数右边的数作为一个区间,借助双指针来判断这三个数是否能够组成三角形,代码如下

class Solution {
public:
    int triangleNumber(vector<int>& nums) 
    {
        sort(nums.begin(), nums.end());
        int count = 0;
        int n = nums.size();
        for (int i = n-1; i >= 2; i--)
        {
            int left = 0;
            int right = i-1;

            while (left < right)
            {
                if (nums[left] + nums[right] > nums[i])
                {
                    count += right - left;

                    right--;
                }
                else
                {
                    left++;
                }
            }
        }

        return count;
    }
};

6. 和为s的两个数

题目链接:LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)

解析:分析题目后,我们可以使用双指针从数组的两端向中间寻找,由于数组是升序的,因此当结果 < target时,left++即可,当结果> target时,right--即可,代码如下

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) 
    {
        int n = price.size();

        int left = 0, right = n-1;
        while (left < right)
        {
            if (price[left] + price[right] < target) left++;
            else if (price[left] + price[right] > target) right--;
            else break;
        }

        return {price[left], price[right]};
    }
};

7. 三数之和

题目链接:15. 三数之和 - 力扣(LeetCode)

解析:这道题要求找到三数之和为0的三元组,那么我们可以先将数组排序,每次固定一个最小的数(最左端),在它右边的剩余区间中借助双指针找到符合nums[left] + nums[ight] == nums[min]的数,代码如下

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        // 借助set去重
        set<vector<int>> res;
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for (int i = 0; i < n-2; i++)
        {
            int left = i+1;
            int right = n-1;
            while (left < right)
            {
                if (nums[left] + nums[right] < -nums[i]) left++;
                else if (nums[left] + nums[right] > -nums[i]) right--;
                else
                {
                    vector<int> tmp = {nums[i], nums[left++], nums[right--]};
                    res.insert(tmp);
                }
            }
        }

        vector<vector<int>> ret;
        for (auto& e : res) ret.push_back(e);

        return ret;
    }
};

8. 四数之和

题目链接:18. 四数之和 - 力扣(LeetCode)

解析:对于四数之和我们可以采取和三数之和类似的处理方法,即先将数组排序,固定一个最小的数(最左端数),在剩下的区间内再去固定另一个最小值,再借助双指针寻找并判断四数是否满足对应关系,代码如下

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        sort(nums.begin(), nums.end());
        set<vector<int>> res;
        int n = nums.size();
        for (int i = 0; i < n - 3; i++)
        {
            for (int j = i+1; j < n-2; j++)
            {
                int left = j+1;
                int right = n-1;
                while (left < right)
                {
                    int a = nums[i];
                    int b = nums[j];
                    int c = nums[left];
                    int d = nums[right];

                    long long t_left = nums[left] + nums[right];
                    long long t_right = target;
                    t_right -= nums[i];
                    t_right -= nums[j];
                    
                    if (t_left < t_right) left++;
                    else if (t_left > t_right) right--;
                    else 
                    {
                        vector<int> tmp = {a,b,c,d};
                        res.insert(tmp);

                        left++;
                        right--;
                    }
                }
            }
        }

        vector<vector<int>> ret;
        for (auto& e : res) ret.push_back(e);

        return ret;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值