【优选算法】双指针

目录

一、移动零

题目描述:


思路讲解:

定义两个变量cur和prev来将数组分为三个区间,[0元素范围区间][非0元素范围区间][待处理区间],prev指向非0元素范围区间中最后一个非0元素,cur 遍历数组,找到非0元素,找到后与prev后面一个元素进行交换,直到cur将数组完全遍历完,则完成本题。


编写代码:

cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int prev = -1 , cur = 0;
        // prev指向非0元素范围区间中最后一个非0元素
        // cur 遍历数组,找到非0元素
        // prev 和 cur 的作用是将 num 分为三段
        
        while(cur < nums.size())
        {
            if(nums[cur] != 0)
            {
                swap(nums[++prev],nums[cur]);
            }
            // 无论是否交换cur都需要++
            cur++;
        }
    }
};

二、复写零

题目描述:


思路讲解:

本道题的要求是在原数组 的要求下完成,那么这道题会变得不是非常的简单。

这里使用双指针的方法从左往右开始复写,遇到非零元素则复写一个非零元素,遇到为零的元素则复写两个零,但是从下图中我们发现只要遇到一个零就会导致后面所有的数字全部出现问题,那么下面我们换一个思路。

这里双指针从左向右开始复写行不通,那么这里使用从右向左开始复写试试。

当从右向左开始复写我们首先要解决的问题是从哪个元素开始从后往前,并且会出现凑巧复写两个零时只有一个位置能够复写,那么就会出现越界的问题。

下面一张图片左右两边是分别关于非越界和越界两种情况种查找的过程。

那么这里开始解决问题,首先使用双指针来寻找从哪个元素开始从前往后复写。

定义两个变量prev = -1cur = 0,那么继续向后循环下面的步骤

  1. 访问cur位置的元素
  2. 通过cur位置的元素判断prev移动一步(非零元素)还是两步(零)
  3. 判断prev是否到达数组的末尾
  4. 若prev未到达数组末尾,cur++,重复上面操作,到达末尾则结束

但是上面的操作还是存在漏洞,那就是并未解决可能会出现越界的问题。

出现prev越界的情况一定是最后一个复写的元素为零时,剩下的空间不足值,导致prev越界,那么我们这里做一个特殊判断,当越界时,cur--prev-=2,而数组最后一个元素赋值为0,那么这里就是解决从哪里开始复写和越界的问题了。

剩下的问题就很简单了,知道了从哪里开始从后往前开始复写,相信大家看完下面的代码就能理解了,下面还有一张关于越界和非越界情况的图解,希望能够对大家有所帮助。


编写代码:

cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int prev = -1, cur = 0;
        // 因为数组的末尾的位置是size-1
        // 而prev的位置只能是size-1(末尾)或是size的位置(越界)
        // 所以prev的位置不能超过size-1,否则prev则会变为为size和size+1

        // 由于size()的返回值是size_t
        // 与int比较时会发生类型提升
        // 导致-1 > 0,所以这里将size()强转为int进行比较
        while (prev < (int)arr.size() - 1)
        {
            if (arr[cur] != 0)
                prev++;
            else
                prev += 2;

            // 当prev到数组尾或越界时,cur不需要++
            if (prev < arr.size() - 1)
                cur++;
        }

        // 这里prev越界了,需要-=2
        // cur需要向前移动改变复写开始的位置
        if (prev == arr.size())
        {
            prev -= 2;
            cur--;
            arr[arr.size() - 1] = 0;
        }

        while (cur >= 0)
        {
            if (arr[cur] == 0)
            {
                arr[prev--] = 0;
                arr[prev--] = 0;
            }
            else
            {
                arr[prev--] = arr[cur];
            }
            cur--;
        }
    }
};

三、快乐数

题目描述:


思路讲解:

我以两个示例画个图来解决问题,大家可能会认为这个图会有点眼熟。

相信大家看到这个形状就会感到非常的熟悉,没错!这就是链表中学到过的判断链表是否带环,思路就是快慢双指针。首先有一个链表,下面是带环定义两个变量slow和fast,由于循环条件的问题,slow与fast不能相等,所以使slow与fast分别指向头部的元素和头部的元素的下一个,那么下面就是循环部分了,fast每一次走两步,slow每一次走一步,如果链表带环,那么fast与slow一定会相遇,那么相反,若fast走到尾部则说明链表中不带环。

一般我们看到这道题会会想到三种情况,但是由于题目传来的数字是int类型的,所以int数字最大的情况是2^31,也就是2147483647,而这道题中下一个数字是上一个数字每个数字的平方和,那么我们将数字再次放到为9999999999,那么下一个数最大为 9^2 * 10 = 810,那么就说明下一个数得到的范围是0~810,那么就可以得到这道题中这些数字是一点会有循环的,那么第三种情况是不存在的。

虽然说这里并没有真实的链表,但是这里还是可以复用上面的思想,定义两个变量slow和fast,由于循环条件的问题,slow与fast不能相等,所以使slow与fast分别赋值为第一个数字和第二个数字,fast每次变化两次,slow每次变化一次,由于本题一定会有循环的数字,那么这里我们判断当fast与slow相遇时,当前的数字和下一个数字是否相等,相等则符合条件,因为只有1变化后不变。


编写代码:

cpp 复制代码
class Solution {
public:
	// 得到n每一位的平方和
    int getSum(int n)
    {
        int sum = 0;
        while (n)
        {
            int ret = n % 10;
            sum += ret * ret;
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        int slow = n, fast = getSum(n);
        while (slow != fast)
        {
            slow = getSum(slow);
            fast = getSum(getSum(fast));
        }

        if (slow == getSum(slow))
            return true;
        else
            return false;
    }
};

四、 盛最多水的容器

题目描述:


思路讲解:

由图可知,存储水量的大小 (V)= 两根垂线中矮的(h) * 两个垂线的距离(l),那么我们这里的思路是定义两个变量left和right分别指向数组的头和尾,再定义一个变量MaxV用来记录最大储水量。我们可以发现一个规律,left和right向中间移动时,l一定减小,下面分下面两种情况:

  1. 两根垂线中高的向中间移动,由于矮的高度一定不变,若高的变得比原来更高,但是矮的已经确定,那么h也就确定了不变,并且由于l减小,那么V就会变小,若高的变得比矮的还矮,那么这里就是hl同时减小,那么V一定减小,总而言之,两根垂线中高的向中间移动,V一定变小。
  2. 两根垂线中矮的向中间移动,由于的高的高度一定不变,若矮的变的更矮,那么V就会变小,若矮的高度变高,那么V可能变大。

看完上面两种情况后,我们知道,两根垂线中高的向中间移动,V一定变小,两根垂线中矮的向中间移动,V不一定变小,所以这里定义一个变量MaxV记录最大的储水量,定义一个变量V记录每次储存水量的大小,每次记录完后两根垂线中矮的向中间移动,若V比MaxV大,将V赋值给MaxV,直到两根垂线相遇后结束,最后返回MaxV的值则完成本题 。


编写代码:

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

五、有效三角形的个数

题目描述:


思路讲解:首先将数组排序一下变为升序,首先定义三个变量MaxToMini、left 和 right,首先将MaxToMini定位到数组的最右边边,left 和 right分别定位到 数组的最左边和 MaxToMini的左边,再定义一个count记录有效三角形的个数。

这里的思想是,若left 和 right 指向位置的数字加起来比 MaxToMini 指向位置的数字大 ,又 left 到 right 位置上的数字都比 left 位置上的数字大或相等 ,所以这一段上的数字一定能与 MaxToMini 和 right位置上的数字组成一个三角形,将 left 和 right 之间的数字的个数加到 count 中,再将right--从新开始判断。若 left 和 right 指向位置的数字加起来比 right指向位置的数字小,则将left向右移动,使left位置指向的数字增大,最终变为上面一种情况或是这一次的三个数字不能组成三角形。当left 与 right相遇,那么这次内循序结束,使MaxToMini向左移动,重新定位left和right,直到MaxToMini移动到最左边后完成外循环,最后返回count则完成本道题。


编写代码:

cpp 复制代码
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        // 首先将数组排为有序
        sort(nums.begin(),nums.end());

        int MaxToMini = nums.size()-1;
        int left = 0 , right = MaxToMini - 1;
        int count = 0;

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

            MaxToMini--;
            right = MaxToMini-1;
            left = 0;
        }

        return count;
    }
};

六、查找总价格为目标值的两个商品

题目描述:


思路讲解:

首先将数组排序一下变为升序,定义两个变量 left 和 right ,分别定位在数组的最左边和最右边,若两个位置指向数字之和小于price,那么 left 向右移动,使两者之和变大,若两个位置指向数字之和大于price,那么 right 向左移动,使两者之和变小,若两个位置指向数字之和等于price,将这两个数字插入到vector中,最后返回vector完成本题。


编写代码:

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

七、三数之和

题目描述:


思路讲解:

首先将数组排序一下变为升序,定义三个变量 MinToMaxi 、 left 和 right,分别定位到数组的最左边、MinToMaxi的右边和数组的最右边和,本题中不能存在相同的三元组若三个位置上的数字之和大于0 ,那么使right向左移动,并且移动时跳过与上一个数字相同元素,使三数之和变小,若三个位置上的数字之和小于0 ,使left向右移动,并且移动时跳过与上一个数字相同元素,使三数之和变大,若三个位置上的数字之和等于0 ,则将这三个数字放入vector中,为了去重,将这三个数字放到vector后,left 和 right同时向中间移动,并且在移动时需要跳过被放入三元组中相同的元素,直到left与right相遇后,本次内循环结束,MinToMaxi向右移动,并且为了去重,MinToMaxi也需要跳过相同的元素,移动后重新定位left和right,开始新一轮的内循环,重复上面的操作,直到MinToMaxi移动到最右边时,完成外循环,返回vector后完成本题。

本题还有一个小优化,由于 MinToMaxi 指向位置的数字是三个中最小的,若这个数字都大于0那么后面的循环中不可能出现三数之和为0的情况,可以直接结束外循环。


编写代码:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int MinToMaxi = 0;
        int left = MinToMaxi + 1, right = nums.size() - 1;
        int pos = 0;
        vector<vector<int>> ans;

        // 这里nums[MinToMaxi] <= 0算是一个小小的优化
        // 由于该数组是被排序过的,当三个数字中最小的
        // 也大于0,那么三数之和就不可能为0
        while (MinToMaxi < nums.size() && nums[MinToMaxi] <= 0)
        {
            while (left < right)
            {
                if (nums[left] + nums[right] > -nums[MinToMaxi])
                {
                    // 将相同元素全部略过
                    while (right > 1 && nums[right] == nums[right - 1])
                    {
                        right--;
                    }
                    // 移到下一个不相同的元素
                    right--;
                }
                else if (nums[left] + nums[right] < -nums[MinToMaxi])
                {
                    // 将相同元素全部略过
                    while (left < nums.size() - 1 && nums[left] == nums[left + 1])
                    {
                        left++;
                    }
                    // 移到下一个不相同的元素
                    left++;
                }
                else
                {
                    vector<int> tmp;
                    tmp.push_back(nums[MinToMaxi]);
                    tmp.push_back(nums[left]);
                    tmp.push_back(nums[right]);

                    ans.push_back(tmp);

                    // 这里防止right-1 < 0,出现越界的情况
                    while (right > 1 && nums[right] == nums[right - 1])
                    {
                        right--;
                    }
                    right--;

                    // 这里防止left + 1 >= nums.size() ,出现越界的情况
                    while (left < nums.size() - 1 && nums[left] == nums[left + 1])
                    {
                        left++;
                    }
                    left++;
                }
            }
            // 更新指向位置

            while (MinToMaxi < nums.size() - 1 && nums[MinToMaxi] == nums[MinToMaxi + 1])
            {
                MinToMaxi++;
            }
            MinToMaxi++;

            left = MinToMaxi + 1;
            right = nums.size() - 1;
        }
        return ans;
    }
};

八、四数之和

题目描述:


思路讲解:

本题思路与上面三数之和的思路基本类似,上面题中每次判断需要三个数,每次内循环中,只需要固定一个数,移动两个数 ,而本道题判断需要四个数,所以需要固定两个数,移动两个数。所以这里与上面题的思路相似,只需要在嵌套一层循环,再固定一个数,那么这题就不做详细的讲解了。


编写代码:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        int size = nums.size();
        vector<vector<int>> ans;
        int MinToMaxi1 = 0 , MinToMaxi2 = 1;
        int left = 2 , right = size-1;
        while(MinToMaxi1 < size-1)
        {
            while(MinToMaxi2 < size-1)
            {
                while(left < right)
                {
                    long long tmp = (long long)target - nums[MinToMaxi1] - nums[MinToMaxi2];
                    if(nums[left] + nums[right] > tmp)
                    {
                        while(right > 1 && nums[right] == nums[right-1])
                        {
                            right--;
                        }
                        right--;
                    }
                    else if(nums[left] + nums[right] < tmp)
                    {
                        while(left < size - 1 && nums[left] == nums[left+1])
                        {
                            left++;
                        }
                        left++;
                    }
                    else
                    {
                        vector<int> ret;
                        ret.push_back(nums[MinToMaxi1]);
                        ret.push_back(nums[MinToMaxi2]);
                        ret.push_back(nums[left]);
                        ret.push_back(nums[right]);

                        ans.push_back(ret);

                        while(right > 1 && nums[right] == nums[right-1])
                        {
                            right--;
                        }
                        right--;

                        while(left < size - 1 && nums[left] == nums[left+1])
                        {
                            left++;
                        }
                        left++;
                    }
                }
                while(MinToMaxi2 < size - 1 && nums[MinToMaxi2] == nums[MinToMaxi2+1])
                {
                    MinToMaxi2++;
                }
                MinToMaxi2++;

                left = MinToMaxi2 + 1;
                right = size - 1;
            }
            while(MinToMaxi1 < size - 1 && nums[MinToMaxi1] == nums[MinToMaxi1+1])
            {
                MinToMaxi1++;
            }
            MinToMaxi1++;

            MinToMaxi2 = MinToMaxi1 + 1;
            left = MinToMaxi2 + 1;
            right = size - 1;
        }
        return ans;
    }
};

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。

希望大家以后也能和我一起进步!!🌹🌹

如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

相关推荐
Water_Sunzhipeng8 分钟前
The 3rd Universal CupStage 15: Chengdu, November 2-3, 2024(2024ICPC 成都)
算法
席万里13 分钟前
C++图案例大全
数据结构·c++·算法
李小白6619 分钟前
二叉树的练习题(中)
java·数据结构·算法
昇腾CANN22 分钟前
Ascend C算子性能优化实用技巧05——API使用优化
c语言·开发语言·性能优化
沐泽Mu33 分钟前
嵌入式学习-C嘎嘎-Day03
c语言·开发语言·c++·学习
yyycqupt44 分钟前
多路转接之poll
服务器·c++·后端·网络协议
_OLi_1 小时前
力扣 LeetCode 347. 前K个高频元素(Day5:栈与队列)
算法·leetcode·职场和发展
LabVIEW开发1 小时前
LabVIEW开发相机与显微镜自动对焦功能
算法·计算机视觉·labview知识
秀儿还能再秀1 小时前
支持向量机SVM——基于分类问题的监督学习算法
算法·机器学习·支持向量机·学习笔记
我不是程序猿儿1 小时前
【C++】关于使用系统库fileapi.h的readfile,及’读‘时间耗时太长的解决方案
c++·stm32·单片机