一、移动零
1、使用场合
当一个题目给你一个类似与指针的结构,并要你做出类似于划分数组的行为,我们这个时候就可以使用双指针。
2、两个指针的作用
具体题目需要具体思考
cur : 一般是从左到右扫描数组,遍历数组。
dest :以及处理的区间,规定条件的最后一个位置。
3、例题
(1)模块划分
[0,dest] [dest+1,cur-1] [cur ,n-1]
处理过的 待处理
(2)代码实现
class Solution {
public:
void moveZeroes(vector<int>& nums)
{
int dest = -1;
int cur = 0;
while(cur<nums.size())
{
if (nums[cur] == 0)
{
cur++;
}
else
{
dest++;
swap(nums[cur],nums[dest]);
cur++;
}
}
}
};
(3)代码解析
cur : 一般是从左到右扫描数组,遍历数组。
dest :以及处理的区间,零的最后一个位置。
我们首先将cur定义在0的位置,dest定义在-1的位置,让cur遍历数组,如果是0,cur++
不是零就让dest++,然后将dest和cur所指向的数组交换,以此来达到三个区间始终保持如下的 模式
[0,dest] [dest+1,cur-1] [cur ,n-1]
处理过的 待处理
二、移动零
1、使用场合
类似与移动数组的操作都可以使用双指针
2、两个指针的作用
思路:如上图所示,我们需要对数组就地进行修改,但我们可以从异地双指针中寻找思路
一般情况下,我们是让cur遍历数组,des指向另外一个数组,当cur指向的字母是非零数字时,des复写一遍,当他时零时,des复写两遍,这样就解决了问题,我们可以把这样的思路应用在同一个数组中即:
但是如果我们遇到零复写两遍的时候,会覆盖后面的值,所以我们想到可以从前向后覆盖,让cur指向需要复写的最后一个字母,des指向最后一个字母
总结:
1. 先找到最后一个“复写”的数
双指针算法:
1. 先判断cur位置的值
2. 决定des向后移动一步还是两步
3. 判断一下des是否已经到结束为止
4.cur++;
2. 处理一下边界情况
如果des在上一阶段越界,我们让cur--;des-=2;在进行下一步
3.从后向前完成复写操作
代码:
class Solution {
public:
void duplicateZeros(vector<int>& arr)
{
int n = arr.size();
int des = -1;
int cur = 0;
while(des< n-1)
{
if(arr[cur]) des++;
else des += 2;
if(des >= n-1) break;
cur++;
}
if(des == n)
{
arr[n-1]=0;
cur--;
des-=2;
}
while(cur>=0)
{
if(arr[cur])
{
arr[des--]=arr[cur--];
}
else
{
arr[des--]=0;
arr[des--]=0;
cur--;
}
}
}
};
三、快乐数
1、题目
2、题目解析
因为题目中说明了,无论是不是快乐数都会进入循环,唯一的不同是快乐数是循环的数是1,而非快乐数循环的不是1(其实无论有没有这句话,我们都可以通过推导知道,每一次将该数替换为它每个位置上的数字的平方和,都会进入循环例如我们举一个例子他的n = 9999999999,他的下一个数是 810,且他以后的数不可能比99999999999再大,所以他每次的值就会在1到810中出现,最多我们循环810次,我们就可以进入循环) 他就是带环问题
3、使用场合
当我们需要数组或者链表带环问题,我们可以使用快慢指针。至于为什么使用快慢指针以及快慢指针关于带环问题的其他应用,在下面的文章中有详细解说
下一个slow指向slow的下一个数据,下一个fast指向fast的下一个数据的下一个数据。
5、代码实现
class Solution
{
public:
int NextNumber(int n)
{
int sum = 0;
while(n)
{
int t = n%10;
sum += t*t;
n/=10;
}
return sum;
}
bool isHappy(int n)
{
int slow = n;
int fast = NextNumber(n);
while(slow != fast)
{
slow = NextNumber(slow);
fast = NextNumber(NextNumber(fast));
}
if(slow == 1)
{
return true;
}
else
{
return false;
}
}
};
四、盛水最多的问题
1、题目
2、题目解析
这里给大家提供两种解法:
解法一:
暴力枚举,我们使用两层for循环,不断遍历使它得出最大的容积。(但是会超时)
解法二:
利用单调性,使用双指针来解决问题
我们可以看到他的高是他的较小值,他的低是他两者之间的距离 ,我们可以将两个指针放在一头一尾,这时候他的低是最大的,但指针往里面移动时,他的底就会缩小,只有他的高增大的时候,他的容积就会增大,所以我们让两个指针的最小的那一个往里面移动,如果容积增大就更新容积
3、代码实现
class Solution {
public:
int maxArea(vector<int>& height)
{
int cur = 0;
int des = height.size() -1;
int v = 0;
while(cur < des)
{
int v1 = min(height[cur],height[des]) * (des - cur);
v = max(v,v1);
if(height[cur]<height[des])
{
cur++;
}
else
des--;
}
return v;
}
};
五、有效三角形的个数
1、题目
2、题目解析
这里给大家提供题目两种解题思路
解法一:通过三层for循环一个一个的遍历,到底有几个(但是时间会超时)
解法二:利用单调性,使用双指针算法来解决问题(最小两边的和大于第三边一定是三角形)
1、我们将他们进行排序,然后你会得到一个升序的顺序表
2、然后再固定一个数
3、再最大的数的左区间内,使用双指针,快速统计符合要求的三元组的个数
例如:
我们一般会遇到两种情况:
当right+left>固定数的时候
因为left是最小的数所以他right加他都可以大于固定数,那么我们知道有right-left个数都是大于固定数的,然后我们将right--
第二种当left+right<=固定值的时候,此时right为可遍历的最大的数都不可以实现三角形,就说明right前面的所有值加上left都不可以实现三角形,所以我们让left++;
当大于left大于right的时候让移动固定数。
代码实现
class Solution {
public:
int triangleNumber(vector<int>& nums)
{
sort(nums.begin(),nums.end());
int x = 0;
for(int i = nums.size()-1; i>=2; i--)
{
int left = 0;
int right = i-1;
while(left<right)
{
if(nums[left]+nums[right]>nums[i])
{
x+= right -left;
right--;
}
else
{
left++;
}
}
}
return x;
}
};
六、和为targt的两个数
1、题目
2、题目解析
这道题和上一道题高度类似同样由两种解法
解法一:暴力解法,两层for循环,循环遍历直到找到何为target的数
解法二:利用单调性,使用双指针算法解决问题:因为该数组是升序的所以我们将指针定在一 头 一 尾此时会出现三种情况 :两者相加大于targe因为left是最小的数,所以left以后的值都 不同和right加使之等于targe,所以right--;如果两者相加小于target因为right是最大的数说明 right之前的数都不能和left相加使之等于targe所以left++;如果等于target返回结果
3、代码实现
class Solution {
public:
vector<int> twoSum(vector<int>& price, int target)
{
int left = 0;
int right = price.size()-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};
}
};
七、三数之和
1、题目
https://leetcode.cn/problems/3sum/description/https://leetcode.cn/problems/3sum/description/
2、 题目解析
这道题和前面几道有异曲同工之妙同样有两种解法
解法一:暴力解法:首先我们要进行排序(方便去重),然后在暴力枚举 ,再利用set去重
解法二:排序 + 双指针:首先排序,在固定一个数,再在该数后面的区间内,利用双指针算法快速找到两个和等于固定数的相反数即可(如何使用双指针同上题一样)。去重的第二种解法:找到一种结果之后,left和right指针要跳过重复元素,当使用完一次双指针算法之后,i也需要跳过重复元素,但是这种算法需要避免越界。
3、代码解答
class Solution
{
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
sort(nums.begin(),nums.end());
vector<vector<int>> ret;
for(int i = 0; i<nums.size(); i++)
{
while(i!=0&&(nums[i]==nums[i-1])&&(i<nums.size()-1))
{
i++;
}
int left = i+1;
int right = nums.size()-1;
int target = -nums[i];
while(left < right)
{
if(nums[left]+nums[right]>target) right--;
else if(nums[left]+nums[right]<target) left++;
else
{
ret.push_back({nums[i],nums[left],nums[right]});
right--;
left++;
while(left<right && nums[right]==nums[right+1])
right--;
while(left<right && nums[left]==nums[left-1])
left++;
}
}
}
return ret;
}
};
八、四数之和
1、题目
https://leetcode.cn/problems/4sum/description/https://leetcode.cn/problems/4sum/description/
2、 题目解析
和上面一题十分相似,有两种解法,
解法一:暴力枚举:首先排序,然后四层for循环 在将结果放入set容器中
解法二:排序+双指针:1、依次固定一个数a 2、在a后面的区间内,在固定一个数b,在b后面的区间内一头一尾各固定双指针找到两个数,是这两个数的和等于target - a - b即可。
3、代码实现
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target)
{
vector<vector<int>> ret;
sort(nums.begin(),nums.end());
for(int a = 0 ; a < nums.size(); )
{
for(int b = a+1;b<nums.size();)
{
int left = b+1;
int right = nums.size()-1;
long long aim =(long long) target - nums[a] - nums[b];
while(left<right)
{
int sum = nums[left]+nums[right];
if((nums[left]+nums[right]) < aim)
{
left++;
}
else if((nums[left]+nums[right] )> aim)
{
right--;
}
else
{
ret.push_back({nums[a],nums[b],nums[left],nums[right]});
left++;
right--;
while(left<right&&nums[left]==nums[left-1]) left++;
while(left<right&&nums[right]==nums[right+1]) right--;
}
}
b++;
while(b<nums.size()&&(nums[b]==nums[b-1])) b++;
}
a++;
while(a<nums.size()&&(nums[a]==nums[a-1])) a++;
}
return ret;
}
};