单位网站 方案搜索引擎技巧
本章先分享关于优选算法的双指针的思路:
主要是以题目来展示常见使用双指针的思路。
ps:
双指针做法:不要被表面所迷惑,它其实是通过用一个数组的下标来充当指针
数组分两块:是⾮常常⻅的⼀种题型,主要就是根据⼀种划分⽅式,将数组的内容分成左右两部分。这种类型的题,⼀般就是使⽤「双指针」来解决
OJ(一)
283. 移动零 - 力扣(LeetCode)
1)题目展示
2)解法思路:
数组分两块:一边非0,一边0,可以使用双指针法。
1.定义两个指针:
cur:从左到右扫描数据,来遍历数组
dest:已处理的区间内,非零元素的最后一个位置。
由下面我们可以看出:
分为三个区间:
[0,dest][]dest+1,cur-1][cur,n-1]
非0 0 未处理
过程
具体做法:
1.cur遇到0,cur++
2.cur遇到非零元素:swap(dest+1,cur),cur++
代码:
class Solution {
public:void moveZeroes(vector<int>& nums) {int cur=0;int dest=-1;for(cur=0;cur<nums.size();cur++){if(nums[cur]){swap(nums[cur],nums[++dest]);}}}
};
OJ(二)
1089. 复写零 - 力扣(LeetCode)
1)题目展示:
2)解法思路:
1.双指针法:
如果「从前向后」进⾏原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数「被覆盖掉」。因此我们选择「从后往前」的复写策略。
但「从后向前」复写的时候,我们需要找到「最后⼀个复写的数」,因此我们的⼤体流程分两步:
i. 先找到最后⼀个复写的数;
ii. 然后从后向前进⾏复写操作
总结:
1.先根据“异地”操作,然后再进行双指针的“就地”操作
2.先找到最后一个复写的数:
双指针算法:
1)先判断cur的值
2)决定dest是走一步还是两步(0两步,非0一步)
3)判断dest是否已经到了结束位置
4)cur++
3. 然后从后向前进⾏复写操作。
1)i. 判断 cur 位置的值:
1. 如果是 0 : dest 以及 dest - 1 位置修改成 0 , dest -= 2 ;
2. 如果⾮零: dest 位置修改成 0 , dest -= 1 ;
2)cur-- ,复写下⼀个位置
4.越界处理:判断 dest 是否越界到 n 的位置
eg:1 0 2 3 0 0(会越界)
如果越界,执⾏下⾯三步:
1. n - 1 位置的值修改成 0 ;
2. cur 向移动⼀步(即cur--);
3. dest 向前移动两步(即dest-=2)
3)代码
class Solution {
public:void duplicateZeros(vector<int>& arr) {int cur=0,dest=-1;int 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;arr[dest--]=0;cur--;}}}
};
OJ快乐数(三)
202. 快乐数 - 力扣(LeetCode)
1)题目展示
2)解法思路
分析题目意思:
看到这里,就与我们之前在数据结构里面OJ题写过的判断链表是否有环那题非常相似的。
那里,我们使用的是快慢指针的方法,实质也是双指针。
十道OJ题帮你深入认识链表-CSDN博客
所以,思路:
双指针的快慢指针:
1)慢指针每次向后移动一位
2.快指针每次向后移动两位
3.判断相遇时的值是否为1即可。
ps:其实这里不可能不成环的。(证明:鸽巢原理:n个巢,n+1个鸽,必有一个巢会有2只鸽子及以上)
3)代码
class Solution {
public:int GetNum(int n){int num=0;while(n){int ret=n%10;num+=ret*ret;n/=10; }return num;}bool isHappy(int n) {int slow=n,fast=GetNum(n);while(slow!=fast){slow=GetNum(slow);fast=GetNum(GetNum(fast));}return fast==1; }
};
OJ(四)
11. 盛最多水的容器 - 力扣(LeetCode)
1)题目展示
2)解法思路
解法1:暴力枚举(超时)即一个一个遍历去找
解法二:利用单调性,来进行使用双指针法解决
设两指针 left , right ,分别指向⽔槽板的最左端以及最右端,此时容器的宽度为 right - left 。由于容器的⾼度由两板中的短板决定,因此可得容积公式 : v = (j - i) * min(height[left], height[right])
为了下面的写法更加简洁,我们取高为h,宽为w,即V=h*w
下面我们就以题目的实例举例:
我们仔细观察会发现:
V=h*w,h由两板的短板决定
如果此时我们固定⼀个边界,改变另⼀个边界,⽔的容积会有如下变化形式:
--->容器的宽度⼀定变⼩。
---> 由于左边界较⼩,决定了⽔的⾼度。如果改变左边界,新的⽔⾯⾼度不确定,但是⼀定不会超
过右边的柱⼦⾼度,因此容器的容积可能会增⼤。
--->如果改变右边界,⽆论右边界移动到哪⾥,新的⽔⾯的⾼度⼀定不会超过左边界,也就是不会
超过现在的⽔⾯⾼度,但是由于容器的宽度减⼩,因此容器的容积⼀定会变⼩的。
因此:左边界和其余边界的组合情况都可以舍去。所以我们可以 left++ 跳过这个边界,继续去判断下⼀个左右边界
总结思路:
1.定义了left和right指针后,算出体积就可以舍去小的那个值了(即left的话就++,right的话就--)
2.高取小那个。
2.接着继续算体积,每次比较那个大就保留哪个。
3)代码
class Solution {
public:int maxArea(vector<int>& height) {int left=0,right=height.size()-1;int ret=0;while(left<right){int v=min(height[left],height[right])*(right-left);ret=max(ret,v);//移动指针if(height[left]<height[right]){left++;}else{right--;}}return ret;}
};
OJ(五)
611. 有效三角形的个数 - 力扣(LeetCode)
1)题目展示
2)解法思路
数学知识:
怎么才能构成三角形?
设a<=b<=c,那么要想构成三角形,则a+b>c
解法1:暴力枚举(弄三层for循环,再检查是否构成三角形,此时的时间复杂度就为O(n^3))
解法二:
1.我们先进行数组优化(即对数组进行排序)
2.接着利用单调性,使用双指针来解决:
1) 先固定最大的数 ----->O(N)
2)在最大数的左区间内,使用双指针法,快速通过符号构成三角形的数O(N)
若如果 nums[left] + nums[right] > nums[i] :
▪ 说明 [left, right - 1] 区间上的所有元素均可以与 nums[right] 构成⽐nums[i] ⼤的⼆元组
▪ 满⾜条件的有 right - left 种
如果 nums[left] + nums[right] <= nums[i] :
▪ 说明 left 位置的元素是不可能与 [left + 1, right] 位置上的元素构成满⾜条件的⼆元组
▪ left 位置的元素可以舍去, left++ 进⼊下轮循环
最后,时间复杂度为O(n^2)效率提高了.
3)代码
class Solution {
public:int triangleNumber(vector<int>& nums) {sort(nums.begin(),nums.end());int n=nums.size();int ret=0;for(int i=n-1;i>=2;i--){int left=0,right=i-1;while(left<right){if(nums[left]+nums[right]>nums[i]){ret+=right-left;right--;}else{left++;}}}return ret;}
};
OJ(六)
LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)
1)题目展示
2)解法思路
解法一:暴力枚举
解法二:利用单调性,使用双指针来找(跟上面的很像)
因为它已经排好序了,所以我们就省去了自己是排序那一步。
定义两个指针min(指向左边),max(右边)
若price[min]+price[max]>target,利用它的单调性,我们是不是就可以直接移动max--了?你想想你连最小的数都大于target了,其他是不是更加大于?
同样的道理:price[min]+price[max]<target ,直接min++。
当price[min]+price[max]==target时,就return {price[left],price[right]};
3)代码
class Solution {
public:vector<int> twoSum(vector<int>& price, int target) {int left=0,right=price.size()-1;int sum=0;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 {};}
};
OJ(七)
15. 三数之和 - 力扣(LeetCode)
1)题目展示
2)解法思路
解法1:排序+暴力枚举+使用set(去重)
解法2:排序+双指针
1.排序
2.固定一个数a(我们从最左边第一个数先固定)
3.在固定的数的后面区间内,利用双指针,快速找到两个数的和等于-a
跟上面的题也是类似的,利用它的单调性,优化。
如果nums[left]+nums[right]>-a,即可直接right--;
nums[left]+nums[right]<-a,即可直接left++;(原因看上面的解析)
整体大概思路就完成了,
接下来我们来处理细节问题:
1.做到不漏:
解决:找到一种结果后,不要停,缩小区间,继续找
2.做到不重复
解决:
找到一种结果后,left和right都要跳过重复的数
当使用完一次双指针算法后,固定的数也要跳过重复的数。
此外,解决上面的问题的同时,还要注意避免越界的问题(代码中显示)
3)代码
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {sort(nums.begin(),nums.end());int i=0;vector<vector<int>> ret;while(i<nums.size()){int left=i+1,right=nums.size()-1;while(left<right){if(nums[left]+nums[right]>(-nums[i])){right--;}else if(nums[left]+nums[right]<(-nums[i])){left++;}else{ret.push_back({nums[left],nums[right],nums[i]});left++;right--;1.防止越界while(left<right && nums[left-1]==nums[left]){left++;}2.防止越界while(left<right && nums[right+1]==nums[right]) {right--;}}}i++;3.防止越界 while(i<nums.size() && nums[i-1]==nums[i]){i++;}}return ret;}
};
OJ(八)
18. 四数之和 - 力扣(LeetCode)
1)题目展示
2)解法思路
解法1:排序+暴力枚举+set去重(4个循环)
解法2:排序+双指针
1.依次固定一个数a(我们从最左边第一个数开始)
2.在a的后面的区间内,使用“三数之和“找到数,使这三个数的和等于target-a即可
3.那么,根据上面的三数之和题目我们知道:
->固定一个数b
->在b的后面区间,利用双指针找到两个数,使这两个数的和等于target-a-b即可
整体大概思路就完成了,
接下来我们来处理细节问题:
1.做到不漏:
解决:找到一种结果后,不要停,缩小区间,继续找
2.做到不重复
解决:
找到一种结果后,left和right都要跳过重复的数
当使用完一次双指针算法后,固定的数也要跳过重复的数。
此外,解决上面的问题的同时,还要注意避免越界的问题(代码中显示)
3)代码
class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {sort(nums.begin(),nums.end());int n=nums.size();vector<vector<int>> ret;for(int a=0;a<n;){for(int b=a+1;b<n;){int left=b+1,right=n-1;long long aim=(long long)target-nums[a]-nums[b];while(left<right){if(nums[left]+nums[right]>aim){right--;}else if(nums[left]+nums[right]<aim){left++;}else{ret.push_back({nums[left],nums[right],nums[b],nums[a]});left++;while(left<right && nums[left]==nums[left-1]){left++;}right--;while(left<right && nums[right+1]==nums[right]){right--;}}}++b;while(b<n && nums[b]==nums[b-1]){b++;}}++a;while(a<n && nums[a]==nums[a-1]){a++;}}return ret;}
};
好了,双指针法专题到这里就分析完了,希望对你有所进步!
最后,到了本次鸡汤部分:
切勿轻言放弃,最好的东西,总会压轴出场!