一、移动零
该题重要信息:1、保持非0元素的相对位置。2、原地对数组进行操作
思路:双指针算法
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n=nums.size();
for(int des=-1,cur=0;cur<n;++cur)
if(nums[cur]) swap(nums[++des],nums[cur]);
}
};
二、复写零
该题的重要信息:1、不要在超过该数组的长度的位置写入元素(就是不要越界)2、就地修改(就是不能创建新数组)。3、不返回任何东西。
思路:双指针算法
class Solution {
public:
void duplicateZeros(vector<int>& nums) {
int cur=0,des=-1,n=nums.size();
for(;cur<n;++cur){
if(nums[cur]) ++des;
else des+=2;
if(des>=n-1) break;
}
//有可能des会越界,如果越界了就给他修正一下
if(des==n){
nums[--des]=0;
--des;
--cur;
}
//开始从后往前复写
for(;cur>=0;--cur)
if(nums[cur]) nums[des--]=nums[cur];
else{
nums[des--]=0;
nums[des--]=0;
}
}
};
三、快乐数
该题的关键是:将正整数变成他的每位数的平方之和,有可能会一直循环始终到不了1,也有始终是1(快乐数)
思路:快慢双指针算法
以上的两个结论在博主的关于链表带环追击问题的文章里面有分析
class Solution {
public:
int isnum(int n){
int ret=0;
while(n){
int x=n%10;
ret+=x*x;
n/=10;
}
return ret;
}
bool isHappy(int n) {
int slow=n,fast=isnum(n);
while(fast!=slow){
fast=isnum(isnum(fast));
slow=isnum(slow);
}
return slow==1;
}
};
四、盛最多水的容器
思路1、暴力枚举(时间复杂度太高)
class Solution {
public:
int maxArea(vector<int>& height)
{
//暴力枚举
int n=height.size();
int ret=0;
for(int i=0;i<n;++i)
for(int j=i+1;j<n;++j)
ret=max(ret,min(height[i],height[j])*(j-i));
return ret;
}
};
思路2、双指针对撞算法
class Solution {
public:
int maxArea(vector<int>& nums) {
int left=0,right=nums.size()-1,ret=0;
while(left<right){
ret=max(ret,min(nums[left],nums[right])*(right-left));//更新一下
//然后把较小的给去掉
if(nums[left]<nums[right]) ++left;
else --right;
}
return ret;
}
};
五、有效三角形的个数
思路1:升序+暴力枚举
思路2:升序+利用双指针算法
class Solution {
public:
int triangleNumber(vector<int>& nums) {
int n=nums.size();
if(n<3) return 0;
sort(nums.begin(),nums.end());
//从最后一个数开始固定 a+b>c 说明a到b前面的数字+b都可以 --b
//如果a+b<=c 说明b到a后面的数字+a都不行,直接++a
int ret=0;
for(int c=n-1;c>=2;--c){
int a=0,b=c-1;
while(a<b){
if(nums[a]+nums[b]>nums[c]){
ret+=b-a;
--b;
}
else ++a;
}
}
return ret;
}
};
六、查找总价格为目标值的两个商品
. - 力扣(LeetCode)查找总价格为目标值的两个商品
思路1:两层for循环找到所有组合去计算
思路2:利用单调性,使用双指针算法解决问题
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n=nums.size();
int left=0,right=n-1;
while(left<right){
int x=nums[left]+nums[right];
if(x==target) return {nums[left],nums[right]};
else if(x<target) ++left;
else --right;
}
return {};
}
};
七、三数之和
解法1:排序+暴力枚举+set去重
解法2:排序+双指针
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n=nums.size();
sort(nums.begin(),nums.end());
vector<vector<int>> ret;
for(int i=0;i<n-2;++i){
if(nums[i]>0) break;//优化
if(i>0&&nums[i]==nums[i-1]) continue;//不能跟上一次枚举的数一样
int target=-nums[i];
int left=i+1,right=n-1;
while(left<right){
int sum=nums[left]+nums[right];
if(sum>target) --right;
else if(sum<target) ++left;
else{//等于的时候
ret.push_back({nums[i],nums[left++],nums[right--]});
//去重
while(left<right&&nums[left]==nums[left-1]) ++left;
while(left<right&&nums[right]==nums[right+1]) --right;
}
}
}
return ret;
}
};
八、四数之和
解法1:排序+暴力枚举+set去重
解法2:排序+双指针(和上一题基本一样,无非就是多固定了一个数)
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> ret;
int n=nums.size();
if(n<4) return ret;
sort(nums.begin(),nums.end());
for(int i=0;i<n-3;++i){
if(i>0&&nums[i]==nums[i-1]) continue;//去重
for(int j=i+1;j<n-2;++j){
if(j>i+1&&nums[j]==nums[j-1]) continue;
long t=(long)target-nums[i]-nums[j];
int left=j+1,right=n-1;
while(left<right){
int sum=nums[left]+nums[right];
if(sum<t) ++left;
else if(sum>t) --right;
else{
ret.push_back({nums[i],nums[j],nums[left++],nums[right--]});
//去重
while(left<right&&nums[left]==nums[left-1]) ++left;
while(left<right&&nums[right]==nums[right+1])--right;
}
}
}
}
return ret;
}
};
九,接雨水
思路:(正难则反)雨水的面积=总面积(每层面积相加)-柱子的面积(数组和)
时间复杂度:o(N)
class Solution {
public:
int trap(vector<int>& nums) {
int n=nums.size();
if(n<3) return 0;
int left=0,right=n-1;
int prevh=0;//这个是前置高度
int sum=0;//统计总面积
int z=accumulate(nums.begin(),nums.end(),0);
while(left<right){
//等他俩同时超过之前的高度
while(left<right&&nums[left]<=prevh) ++left;
while(left<right&&nums[right]<=prevh) --right;
sum+=(min(nums[left],nums[right])-prevh)*(right-left+1);
prevh=min(nums[left],nums[right]);
}
return sum-z;
}
};
十、总结
常见的双指针有三种形式:前后指针、对撞指针、快慢指针
1、前后指针:用于顺序结构,一般是两个指针同方向,cur指针用来遍历数组,des指针将数组进行区域划分。(如1、2题)
注意事项:如果是从前往后遍历,要注意dst不能走得太快,否则cur还没遍历到一些数就会被覆盖掉,必要时候可以根据情况从后往前遍历。
2、快慢指针:其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列结构上移动。
这种⽅法对于处理环形链表或数组⾮常有⽤。(如第3题,以及链表带环的问题)
注意事项: 其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使⽤快慢指针的思想。最常用的就是快指针走两步,慢指针走一步。
3、对撞指针:一般用于顺序结构。从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。并且常常会用到单调性!!(如4-9题)
注意事项:对撞指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环)
用双指针策略一般可以比暴力枚举降低一个次方的时间复杂度
第9题还用到了正难则反的策略
如果后面还有关双指针的经典题目,博主会继续在这篇更新的!!