目录
双指针,用于数组中的划分问题
- 说是指针,但指针的定义没那么严格
- 可以是下标,可以是其他字段,只要可以标识某位置即可
移动零
题目描述
意思是 -- 将数组分为两部分,非0和0
- 非0,需要保证其顺序仍为原数组中的顺序
思路
很明显的数组划分,所以:
- 我们一个指针cur用来遍历数组(<cur是已处理的,>=cur还未处理)
- 一个指针pos用来区分非0和0(可以让他指向最后一个非0数,也可以指向第一个0)
代码
class Solution { public: void moveZeroes(vector<int>& nums) { //指向第一个0 int pos=0,cur=0; for(auto it:nums){ if(it!=0){ swap(nums[pos],nums[cur]); ++pos; } ++cur; } //指向最后一个非0 int pos=-1,cur=0; for(auto it:nums){ if(it!=0){ ++pos; swap(nums[pos],nums[cur]); } ++cur; } } };
无论如何,都是要将[指向非0数的cur]与[第一个0]交换
- 所以pos的++操作顺序不同
因为已经处理的数字分为两部分:[非0][0],所以:
- 如果第一个数为0,指向0的pos=0,指向非0的pos=-1
- 如果非0,指向0的pos=1,指向非0的pos=0
- 所以可以看出来,他俩的初始值分别是0和-1(因为之后都要执行++操作,所以取最小值为他们的初始值)
复合零
题目描述
- 按照遍历数组的顺序,非0数不变,遇到0要写2遍
- 不能越界,写到数组结尾就结束
- 不能创建新的数组
思路
原地从左到右遍历数组的话,会覆盖掉后面的数字
但是也不能完全从右向左遍历,因为不是所有的数字都会在新数组里
- 所以,我们可以考虑先从左到右,找到新数组的最后一个数字
- 然后从该数字开始,从右往左进行复写
- 这样既可以保证数字不被覆盖,也可以保证数字的顺序不变
也就是说,其中一个指针cur用于遍历数组,另一个指针pos用于指示当前需要被复写的数字
注意
- 如果是0000000,数字长度是奇数,但由于0复写2次,遍历时一定会让cur在结尾处越界一个单位
- 那么复写时,就会让cur在起始处越界一个单位
- 所以,复写前需要单独处理一下 -- 把那个导致越界的0手动操作一下,不能让他们进入处理普通情况的复写代码
代码
class Solution { public: void duplicateZeros(vector<int>& arr) { int pos=0,cur=-1; int n=arr.size(); while(true){ if(arr[pos]==0){ ++cur; } ++cur; if(cur>=n-1){ //因为0要赋值两次,所以会出现cur移动到n的位置 break; } ++pos; } if(cur==n){ //处理超范围的情况 --cur; //首先将cur指向有效的最后一个数字 arr[cur]=0; //因为只有可能是0导致cur加了两次 --cur;--pos;//cur和pos指向下一个未处理的数字 } while(true){ arr[cur]=arr[pos];//当前位置直接赋值 if(arr[pos]==0){ //是0就多赋值一次 arr[--cur]=0; } --pos; //pos指向下一个位置 if(cur==0){ //如果此时已经到头,说明已经处理完毕,结束 break; } --cur; //指向下一个未处理的数字 } } };
快乐数
题目描述
- 给出一个数字n,会得到一串数字
- 在这串数字上,下一个数字=上一个数字每一位的平方和(eg:19 -> 1^2 +9^2=82)
- 如果最终这个平方和可以=1,就是快乐数
思路
如果是快乐数,说明它的变化情况如图(因为1的平方和=1):
如果不是快乐数,最终也会成环
- 因为
- 如果把每一位都用9替代->9999999999(有10个9),它的平方和=10*9^2=810
- 所以,n的最大平方和不会超过810,说明无论起始值有多大,只要进行运算一次,就不会超过810
- 所以,只要循环下去,必定成环(因为平方和的范围不是无限大的)
- 所以,它的变化情况如图:
所以,我们只要判断环内数字是否=1即可
是不是很像我们的链表成环,只不过我们不需要判断是否成环,也不需要找到开始成环的第一个结点
- 当时我们用的快慢指针,只要两指针相遇,就说明有环
这里也可用类似的思想,只不过判断的是相遇时他俩值是否等于1
代码
class Solution { public: int func(int n){ int res=0; while(n){ int t=n%10; res+=(t*t); n/=10; } return res; } bool isHappy(int n) { int fast=n,slow=n; while(true){ slow=func(slow); fast=func(fast); fast=func(fast); if(slow==fast){ if(slow==1){ return true; } return false; } } } };
盛最多水的容器
题目描述
- 水的容积=宽(两条高的下标之差)*高(数组中的值)
- 从数组中选出任意两个值,找到容积最大值
思路
我们考虑从外向内找
- 从内向外找的话,容易漏,具体从哪开始也是个问题
所以,考虑到宽在减小(两条高在向内移动)
- 必须让高变大,才有可能让容积变大;否则一定是变小
- 所以每次向内探索,都让短边移动,试图找到更长的边
- 最后,每次得到一个容积都和最大值进行比较
代码
class Solution { public: int get_min(int x,int y){ return x<y?x:y; } int maxArea(vector<int>& height) { int left=0,right=height.size()-1; int max=0; while(left<right){ int volume=(right-left)*get_min(height[left],height[right]); if(volume>max){ max=volume; } if(height[left]==get_min(height[left],height[right])){ ++left; } else{ --right; } } return max; } };
有效三角形个数
题目描述
- 判断三角形的条件 -- 短边之和大于最大边
- 注意:重复边也是有效的
思路
因为需要判断短边之和大于最大边
- 那么我们可以每次首先取极值来判断(不然就是全部遍历一遍,妥妥超时)
- 首先需要排序
- 然后每次将最大值视为最大边c
- 然后,取出另外一部分数字的最大/小值,作为短边a,b(也就是双指针left,right)
- 如果a+b>c,那么中间的数字就不需要判断了,他们中的任意一个+b一定>c
- 所以,当前的b就可以统计次数了,然后舍弃掉当前的b,将它左边的一个数作为b,进行下一轮比较
- 如果a+b<=c,说明此时的a不够大,舍弃掉它,将下一个数作为a进行比较
- 当a和b相遇时,就说明以当前的c为最大边的情况已经遍历完毕
- 所以,c左移一个单位
- 重复上述过程,直到凑不出三角形为止
代码
class Solution { public: int triangleNumber(vector<int>& nums) { if(nums.size()<3){ return 0; } sort(nums.begin(),nums.end()); int count=0; for(int c=nums.size()-1;c>=2;--c){ int a=0,b=c-1; while(a<b){ if(nums[a]+nums[b]>nums[c]){ count+=(b-a); --b; } else{ ++a; } } } return count; } };
三数之和
题目描述
- 三元组之间不可重复
思路
和上一道题的操作类似,要求某个特定的和,先排序一定能减少我们的操作
然后确定一个数作为基准数,在剩下的部分找另外两个数
- 如果和=(-基准数),他们组成的三元组之和=0
- 两个数遍历的时候,如果加起来<基准数,左指针就向右移动
- >基准数,右指针就向左移动
而且三元组之间不可以重复
- 那么当我们的指针在移动时,就需要跳过重复的数字(如果不跳过的话,相同的数字会找到相同的组合)
且这里的三个指针都需要去重:
- 基准数如果相同,在剩下数字里找的时候,还是会找到原来的组合
- 双指针a,b在遍历的时候,如果不去重 : 基准数不变,a不变,那么对应的就是特定的b,如果b不去重,就会出现重复的三元组 ; 反过来也是一样
当然,去重的时候因为要移动指针,就很可能出现越界访问
- 比如:0,0,0,0这个例子,一旦去重,就会越界
- 所以需要加上边界控制
注意,剩下的部分里,可能不只有一组三元组符合我们的要求
- 所以我们必须全部遍历完,再找下一轮
代码
class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { vector<vector<int>> res; int n=nums.size(); sort(nums.begin(),nums.end()); //固定数字 int c=n-1; while(c>=2){ int a=0,b=c-1; int comp=-nums[c]; while(a<b){ if(nums[a]+nums[b]>comp){ --b; } else if(nums[a]+nums[b]<comp){ ++a; } else{ res.push_back({nums[a],nums[b],nums[c]}); --b;++a; //去重 while(a<b&&nums[b]==nums[b+1]){ --b; } while(a<b&&nums[a]==nums[a-1]){ ++a; } } } --c; //固定数也需要去重 while(c>=2&&nums[c]==nums[c+1]){ --c; } } return res; } };
四数之和
题目描述
- 和三数之和的要求基本一致(不重复)
思路
比三数之和多了一个数字
- 也就是多了一层循环
- 相当于,每次固定一个数d,然后在剩下的区间里进行三数之和的逻辑
- 而三数之和的逻辑 -- 每次固定一个数c,然后在剩下的区间里,用双指针遍历,直到指针相遇
同样,也需要去重,只不过比三数之和多了一次去重
- 首先双指针需要去重
- 然后是固定数c
- 最后是固定数d
代码
class Solution { public: vector<vector<int>> fourSum(vector<int>& nums, int target) { vector<vector<int>> res; if(nums.size()<4){ return res; } sort(nums.begin(),nums.end()); int d=nums.size()-1; while(d>=3){ //固定数d //三数之和的逻辑: int c=d-1; while(c>=2){ //固定数c long long comp2=(long long)target-nums[d]-nums[c]; //会有target很小,而数组中的值很大的情况,相减会溢出,所以转为ll int a=0,b=c-1; //双指针a,b while(a<b){ long long sum=nums[a]+nums[b]; if(sum<comp2){ ++a; } else if(sum>comp2){ --b; } else{ res.push_back({nums[a],nums[b],nums[c],nums[d]}); ++a;--b; //双指针去重 while(a<b&&nums[a]==nums[a-1]){ ++a; } while(a<b&&nums[b]==nums[b+1]){ --b; } } } //固定数c去重 --c; while(c>=2&&nums[c]==nums[c+1]){ --c; } } //固定数d去重 --d; while(d>=3&&nums[d]==nums[d+1]){ --d; } } return res; } };