双指针思想一般用在数组相关的部分题目中,这种题目有两个特点,一个是不允许开辟新的数组,只允许在原数组上进行操作,第二个是它常常与内部元素的移动相关,现在,我来带着大家刷些题来感受一下
引入方法:状态法:在理解题目时,需要总结出题解的状态,此题的状态:1.非0元素在左,0元素在右 2.相对顺序不变
引入思想:主元思想:在对题目进行操作时,需要首先将题目数据进行划分,比如这道题就可以分为0元素以及非0元素两部分,对不同部分进行操作的方法叫做主元法
开始做题了!主元思想:首先将0元素移到右边试一试,状态法:双指针,一个指针找0,一个指针找非0,如果找到则两者互换,那么,开整
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int left=0;
int right=nums.size()-1;
while(left<right)
{
while(nums[left]!=0&&left<right)
{
left++;
}
while(nums[right]==0&&right>left)
{
right--;
}
swap(nums[left],nums[right]);
left++;
right--;
}
}
};
运行一下
观察一下报错,我们可以发现,这个方法已经实现了状态1,但是元素的相对位置被改变,且我们可以观察到这种改变无法在后期通过简单排序修正,所以此思路是错误的
所以,要尝试对非0元素进行操作了,把非0元素放在左边,开整
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int left=0;
for(int right=0;right<nums.size();right++)
{
if(nums[right]!=0)
{
swap(nums[left],nums[right]);
left++;
}
}
}
};
过了
分析题意:遇到0就重写一次,思路异常简单,遇到0就把下一个位置也赋值为0,后面的元素一次向后移动一个即可,在循环时注意元素的记录,那么开整
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int des = 0;
int n = arr.size();
while (des < n)
{
if (arr[des])
{
des++;
}
else
{
n--;
int x = des + 1;
int sign = -10086;
for (x; x < arr.size()-1; x++)
{
sign = arr[x+1];
if(sign==-10086)
arr[x+1]=arr[x];
else
arr[x+1]=sign;
}
arr[des] = 0;
des++;
arr[des] = 0;
}
}
}
};
通过用例分析一下失败的原因:根本没有控制好字符串的长度导致了多次重写,所以我们要先把字符串的长度确定下来,以免以后出现问题
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int count=0;
int move=0;
while(count!=arr.size())
{
move++;
count++;
if(arr[move]==0)
{
count++;
}
}
此时move指针的长度正好是要操作字符串的长度,那么再想一想,此时的count=arr.size(),而move也比正常的要多移动一次,所以让它们重新指向相应的位置
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int count=0;
int move=0;
while(count!=arr.size())
{
move++;
count++;
if(arr[move]==0)
{
count++;
}
}
count--;
move--;
那么有的兄弟就要问了,如果最后move指向的元素为0怎么办,那么字符串的最后只能输出一个0了,观察一下此时两指针的位置,发现count是n+1而不是n,那么就很简单了,覆写0的时候这种情况就不用覆写了
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int count=0;
int move=0;
while(count<arr.size())
{
if(arr[move]==0)
{
count++;
}
move++;
count++;
}
count--;
move--;
while(move>=0)
{
if(count<arr.size())
{
arr[count]=arr[move];
}
if(arr[move]==0)
{
count--;
arr[count]=0;
}
count--;
move--;
}
}
};
过了
分析题意:当计算时只会出现两种情况,一种是无限循环,一种是得到1,对用例进行纸上模拟会发现,用例不是快乐数的原因是它中间会出现某一个值与前面的值相同,但这个值不为1,从而形成环进行无限循环,之前我们做过一道判断环形链表的题,得出判断环形链表的方法为设置快慢指针观察它们是否相遇,在此题的背景下,一定会相遇(这是一个结论),所以就定义快慢两个指针,一个一步进行两次操作,一个一步进行一次操作,然后对相遇后的值进行判断即可,需要注意的是我们把他们俩的初始值赋值为n,所以要使用do while循环让它们先进入到循环中
class Solution {
public:
int conclude(int n)
{
int sum=0;
while(n!=0)
{
sum+=(n%10)*(n%10);
n=n/10;
}
return sum;
}
bool isHappy(int n) {
int fast=n;
int slow=n;
do
{
slow=conclude(slow);
fast=conclude(conclude(fast));
}while(fast!=slow);
return slow==1;
}
};
算法思想:左右两个指针依次往内部移动,如果相遇则停止,每一步都计算体积并实时更新,移动方法:移动当前值最小的指针,因为此时内部的数据是固定的,如果容器的宽减小的情况下,容器的长要尽可能长才有超越原来体积的机会,所以我们此时选择让短边的指针移动
class Solution {
public:
int maxArea(vector<int>& height) {
int left=0;
int right=height.size()-1;
int sum=0;
while(left<right)
{
sum=max(sum,(right-left)*min(height[left],height[right]));
height[left]<height[right]?left++:right--;
}
return sum;
}
};
5.611. 有效三角形的个数 - 力扣(LeetCode)
将数组进行排序,然后利用三角形两边大于第三边的性质进行判断即可,但是很遗憾,超时了
使用双指针进行优化,对以每个数为长边的三角形进行判断,并且减少循环判断次数
class Solution {
public:
bool check(int i,int j,int k,vector<int>&nums)
{
if(nums[i]+nums[j]>nums[k])
{
return true;
}
return false;
}
int triangleNumber(vector<int>& nums) {
int count=0;
sort(nums.begin(),nums.end());
for(int i=nums.size()-1;i>=2;i--)
{
int left=0;
int right=i-1;
while(left<right)
{if(check(left,right,i,nums))
{
count+=(right-left);
right--;
}
else
{
left++;
}
}
}
return count;
}
};
过了
6.LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)
以前面的刷题经验,一眼发现了有序这个条件,根据上一题,可以直接写出双指针代码
class Solution {
public:
vector<int>ret;
vector<int> twoSum(vector<int>& price, int target) {
ret=vector<int>(2,-1);
int left=0;
int right=price.size()-1;
while(left<right)
{
if(price[left]+price[right]==target)
{
ret[0]=price[left];
ret[1]=price[right];
return ret;
}
if(price[left]+price[right]>target)
{
right--;
}
else
{
left++;
}
}
return ret;
}
};
这道题也是双指针,但有很多细节问题需要处理,第一个就是需要进行去重,一种方法是使用set来去重,另一种方法是在循环中进行去重,在遇到相同值时跳过,跳过要在两步,一步是找到之后,一个是完成双指针算法之后,为了不漏,所以在找到结果后不停止循环
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>>ret;
sort(nums.begin(),nums.end());
for(int d=0;d<nums.size();)
{
if(nums[d]>0)
{
break;
}
int left=d+1;
int right=nums.size()-1;
while(left<right)
{
if(nums[left]+nums[right]+nums[d]>0)
{
right--;
}
else if(nums[left]+nums[right]+nums[d]<0){
left++;
}
else
{
ret.push_back({nums[left],nums[right],nums[d]});
left++;
right--;
while (left < right && nums[left] == nums[left - 1]) ++left;
while (left < right && nums[right] == nums[right +1]) --right;
}
}
d++;
while(d<nums.size()&&nums[d-1]==nums[d])
{
d++;
}
}
return ret;
}
};
和三树之和类似首先判断极限情况,就是当四个数分别位于倒数第四三二一的时候,从而确定四个数的边界,由此可以对三数之和的代码进行改改造,把外层固定一个数的for循环换成两层嵌套的for循环,然后在第二层for循环的内部再进行双指针操作,直接复制三数之和的代码并微调即可
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, long long target) {
sort(nums.begin(),nums.end());
vector<vector<int>>ret;
if(nums.size()<4)
{
return ret;
}
for(long int num1=0;num1<nums.size()-3;num1++)
{
if(num1>0&&nums[num1]==nums[num1-1])
{
continue;
}
for(long int num2=num1+1;num2<nums.size()-2;)
{
if(num2>num1+1&&nums[num2]==nums[num2-1])
{
continue;
}
long int left=num2+1;
long int right=nums.size()-1;
while(left<right)
{
if(nums[left]+nums[right]+nums[num1]+nums[num2]>target)
{
right--;
}
else if(nums[left]+nums[right]+nums[num1]+nums[num2]<target){
left++;
}
else
{
ret.push_back({nums[left],nums[right],nums[num1],nums[num2]});
while (left < right && nums[left] == nums[left +1]) ++left;
while (left < right && nums[right] == nums[right -1]) --right;
left++;
right--;
}
}
num2++;
while(num2<nums.size()&&nums[num2-1]==nums[num2])
{
num2++;
}
}
}
return ret;
}
};
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, long long target) {
sort(nums.begin(),nums.end());
vector<vector<int>>ret;
if(nums.size()<4)
{
return ret;
}
for(long int num1=0;num1<nums.size()-3;num1++)
{
if(num1>0&&nums[num1]==nums[num1-1])
{
continue;
}
for(long int num2=num1+1;num2<nums.size()-2;)
{
if(num2>num1+1&&nums[num2]==nums[num2-1])
{
continue;
}
long int left=num2+1;
long int right=nums.size()-1;
while(left<right)
{
if(nums[left]+nums[right]+nums[num1]+nums[num2]>target)
{
right--;
}
else if(nums[left]+nums[right]+nums[num1]+nums[num2]<target){
left++;
}
else
{
ret.push_back({nums[left],nums[right],nums[num1],nums[num2]});
while (left < right && nums[left] == nums[left +1]) ++left;
while (left < right && nums[right] == nums[right -1]) --right;
left++;
right--;
}
}
num2++;
while(num2<nums.size()&&nums[num2-1]==nums[num2])
{
num2++;
}
}
}
return ret;
}
};
过了吗?过不了一点
笔者很好奇,此题不管有什么方法,都会用到起码四个位置的加法比较,笔者不知道为什么会传入这么大的几个数,这样加法是必然过不了的,除非这题也要使用大数加法的计算,而这种题要搞这种是很无语的,日前力扣更新了一下,避免使用long sum存储结果导致逃避大数加法的情况,服
9.来总结一下双指针吧:双指针的应用场景:数组或字符串进行操作而不使用额外空间的,使用判断环的方法的,明显左右两侧同时操作的,在数组中执行各种运算的
双指针的编写前提:在纸上复现用例的形成过程
双指针的知识储备:熟记移动零,复写零,三数之和三个经典模板
好啦,就到这里了,拜拜亲宝们