基础算法:双指针
目录
一、移动零
二、复写零
三、快乐数
四、盛最多水的容器
五、有效的三角形的个数
六、查找总价格为目标值的两个商品
七、三数之和
八、四数之和
双指针最核心的一点往往是单调性。如此元素和下标映射地位相等。
一、移动零
283. 移动零 - 力扣(LeetCode)
两个指针从0开始,cur往后遍历数组,遇到非0元素,就把这个元素和dest位置元素交换,使得非0元素全部按照顺序放到前面。
class Solution {
public:void moveZeroes(vector<int>& nums) {for(int cur=0,dest=0;cur < nums.size();++cur){if(nums[cur]){swap(nums[cur],nums[dest++]);}}}
};
二、复写零
1089. 复写零 - 力扣(LeetCode)
可以先找到模拟后末尾的那个数,方法是遍历数组,遇到0的时候,一个指针cur正常+1,一个指针dest+=2。
最后修改数组的时候,就从cur开始往前遍历,cur是非0元素,那nums[dest]=这个元素,然后两个指针都减一步,是0,就让dest位置开始往前两个元素位置都是0,cur减去一。
注意:因为dest涉及处理了两步的问题,很可能会导致越界。
因此写代码的时候在找倒数那个位置的元素之后,要先判断dest是否越界,越界就说明cur的最后一个位置是0,导致其从下标为n-2的位置加了两次导致越界,这时将数组最后一个元素修改为0,然后cur减去1,dest减去2即可;此外在后续往前修改数组过程中,遇到0,dest第二次移动也可能造成越界,比如奇数个0的情况,因为往后加会导致越界,所以按之前的思路,最后一个位置为0,会从倒数第二个位置开始往前填表,导致最后一个空的时候第二次减的时候dest越界变成-1,所以第二次减也要判断。
class Solution {
public:void duplicateZeros(vector<int>& arr) {//cur遍历数组,dest表示填表的位置int cur=0,dest=-1,n=arr.size();while(cur<n){if(arr[cur])++dest;else dest+=2;if(dest>=n-1)break;++cur;}//修改前特判一下if(dest==n){arr[n-1]=0;--cur;dest-=2;}//开始修改数组while(cur>=0){if(arr[cur])arr[dest--]=arr[cur];else {arr[dest--]=0;if(dest>0)arr[dest--]=0;}--cur;}}
};
三、快乐数
202. 快乐数 - 力扣(LeetCode)
从题干所知,一个数的最终结果只有两个,一个是可以得出1的,于是一直等于1循环下去,另一种就是不管如何都得不到1,在某次求和后得到之前的一次数据,造成循环。
思考有没有第三种情况,即一个数据通过各个位置平方求和后,不断往后,从来没有循环过,不难发现,一个很小的数,各项平方后就会变成比它还大的数,比如2,3,4,而比较大的数据,操作后往往会变成更小的数,于是就可以发现一个小数不断操作会变大,再到某次操作后又会变小,于是在变小变大间不断重复,也就是说,所有的数据是在一个既定的空间内也就是 1到一个最大的数 这个区间内变化的,有的数操作后得不到1,就会在 2到最大的数 这个区间不断变大变小,总会遇到之前相同的数字,就造成循环,得到1的就会一直是1了,所以无限不循环的不存在,其实也可以想想,如果有这种情况的话,那写代码不是死循环了,程序可终止不了哈哈哈。
数据就形成了“环”,不经联想之前在学习链表过程中,写的一道判断环形链表的题目,于是可以想到快慢指针的操作,这样在循环的环里fast和slow总能相遇,只需要判断相遇的这个数是不是1就可以。因为得不到1的不管怎么求和都得不到1,得到1再次操作还是为1。
因为要对数据进行直接操作和判断,而且我们还要得到数字各个位置的数据进行平方求和,第一思路是用数组来做,但是这操作将会非常繁琐。
对于要取数字各个位置的数进行操作,我们可以用while循环,通过%10得到末位的数字,然后再除10去掉这一位,条件只需该数大于0即可。
于是操作就很简单了,代码如下:
class Solution {
public:int sum(int n) {int tmp = 0, sum = 0;while (n) {tmp = n % 10;sum += tmp * tmp;n /= 10;}return sum;}bool isHappy(int n) {int slow = n;int fast = sum(n); //让fast指向slow之后操作的任意一次while (slow != fast) {fast = sum(fast); //先操作fastfast = sum(fast);slow = sum(slow);}return slow == 1;}
};
四、盛最多水的容器
11. 盛最多水的容器 - 力扣(LeetCode)
在一个区间内,取两端数据,比较一下看下哪个更小,更小的那个不用保留,先记录容积,再直接往数组长度缩小的方向前进,因为如果保留更小的由v=h x w,w保持递减,h,如果遇到大于等于该数的,h不变,如果遇到小于该数的,h减小,这样无论如何保存小数遍历的过程都是不需要的,可以直接跳过,不断重复这个过程就能得到一堆容积,最终在这些容积里边找最大值即可。
class Solution {
public:int maxArea(vector<int>& height) {int right = height.size() - 1;int left = 0;int max = 0;int mul = 0;while (left != right) {if (height[left] < height[right]) {mul = (right - left) * height[left];++left;} else {mul = (right - left) * height[right];--right;}if (mul > max) {max = mul;}}return max;}
};
五、有效的三角形的个数
611. 有效三角形的个数 - 力扣(LeetCode)
暴力算法应该是枚举每三个数据,然后三个数进行比对,这样复杂度直接到达N的3次方相当高。
暴力算法枚举的同时无法筛除无效数据,且三数比对时因为无法判断三数的大小关系还需要对三个数进行枚举判断。
思考,由于我们要对三数的大小关系进行判断,如果我们事先将数据排序好(双指针算法的核心),那么只需要三个数最后的位置的那个数据比前两个大就行,如果排序好,那么我们就可以创建三个指针,让最大的指针固定我们要的三个数中的最大的那个数,然后left从数据起点开始遍历,right从固定的最大数前面那个数开始遍历,因为要大于固定数据,我们再固定right指向的更大值,如果left+righ>固定最大数,那么从left到right-1位置的这些数+right位置的数就都能>固定数据,这样就减少了不必要的枚举,如果left+right<=固定最大数,则left++,若大于则sum+=区间长度,然后right--。这样就降低到了N方。其实这里用到了贪心策略。
class Solution {
public:int triangleNumber(vector<int>& nums) {//排序数据,方便大小对比sort(nums.begin(), nums.end());int n = nums.size();int max = n - 1, sum = 0;int left,right;while (max > 1){right = max - 1; //更新right,leftleft=0;while (left < right) {if (nums[max] < nums[left] + nums[right]) {sum += right - left;--right;} else {left++;}}--max;}return sum;}
};
六、查找总价格为目标值的两个商品
LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)
因为数组有序的特性,最先想到利用单调性,使用双指针解决问题。
left和right指向数组两端,计算和,大于right--,小于left++,思路很简单,实现如下:
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){--right;}else if(price[left]+price[right]<target){++left;}else { //直接这样返回就行,不用创建一个数组,记得用{}return {price[left],price[right]};}}//照顾编译器return {-1,-1};}
};
七、三数之和
15. 三数之和 - 力扣(LeetCode)
思路和前一题一样,先对数据排序,然后利用数据的单调性使用双指针算法,不同之处就是要去重,去重思路也很简单,插入完一组数据后,不管是left和right还是固定数,只要和left-1和right+1和res-1相同,(如果是创建变量记录该值,变量在每次遍历开始时需要更新)直接跳过就行,因为这里涉及到了跳过的操作,那么就还需要再进行越界判定。另外固定数>0就可以终止循环了,因为数据已经排序好了,如果固定数都大于0,那么left和right相加不可能小于0了。
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> ans;//排序sort(nums.begin(),nums.end());int n=nums.size();int res=0,left,right;while(res<n&&nums[res]<=0) //因为排序好了,固定数大于0直接结束{left=res+1;right=n-1;while(left<right){int sum=nums[left]+nums[right],target=-nums[res];if(sum>target)--right;else if(sum<target)++left;else{ans.push_back({nums[res],nums[left],nums[right]});++left;--right;//注意越界判断while(left<right&&nums[left]==nums[left-1])++left; while(left<right&&nums[right]==nums[right+1])--right;}}++res;//注意越界判断while(res<n&&nums[res]==nums[res-1])++res; }return ans;}
};
八、四数之和
18. 四数之和 - 力扣(LeetCode)
和上一题思路一致
class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> ans;sort(nums.begin(),nums.end());int n=nums.size();int left,right,edage1=0,edage2;while(edage1<n-3){edage2=edage1+1;long long target1=target-nums[edage1];while(edage2<n-2){left=edage2+1;right=n-1;while(left<right){long long sum=nums[left]+nums[right],target2=target1-nums[edage2];if(sum>target2)--right;else if(sum<target2)++left;else{ans.push_back({nums[edage1],nums[edage2],nums[left],nums[right]});++left,--right;//去重1while(left<right&&nums[left]==nums[left-1])++left;while(left<right&&nums[right]==nums[right+1])--right;}}//去重2++edage2;while(edage2<n-2&&nums[edage2]==nums[edage2-1])++edage2;}//去重3++edage1;while(edage1<n-3&&nums[edage1]==nums[edage1-1])++edage1;}return ans;}
};
双指针end...