目录
本文对双指针算法在面试中的常见应用进行了详细分类和解析,帮助读者掌握双指针的解题技巧和底层逻辑。双指针可以分为相向双指针、同向双指针和背向双指针,分别适用于不同的问题场景,一下将一一列举。通过本文的学习,帮助读者可以更好地理解双指针的核心思想,并灵活运用于实际面试中。PS:本篇博客中的所有题目均来自于灵茶山艾府 - 力扣(LeetCode)分享的题单。
相向双指针
与滑动窗口相反,滑动窗口是同向双指针,left和right都向右或向左移动;相向双指针指的是两个指针都向中间进行移动。下面借助题目进行理解和剖析。
344. 反转字符串
题解:经典的现象双指针问题,使用前后指针进行字符串的交换即可。
125. 验证回文串
题解:依旧是使用相向双指针,但是要注意跳过非字母数字字符,还要注意前后字符进行比较的时候是不区分大小写的。
class Solution {
public:
bool isPalindrome(string s) {
//依旧是使用同向双指针来进行实现
int left=0,right=s.size()-1;
while(left<right)
{
while(right>left&&(s[right]<'a'||s[right]>'z')&&(s[right]<'A'||s[right]>'Z')
&&(s[right]<'0'||s[right]>'9')) right--;
while(right>left&&(s[left]<'a'||s[left]>'z')&&(s[left]<'A'||s[left]>'Z')
&&(s[left]<'0'||s[left]>'9')) left++;
if(tolower(s[left])!=tolower(s[right])) return false;
left++,right--;
}
return true;
}
};
1750. 删除字符串两端相同字符后的最短长度
题解:如果左右两边字符相同进行循环删除接口,当左右两边字符不同是停止,返回字符串长度。
class Solution {
public:
int minimumLength(string s) {
//使用指针找到前后字符不同的位置后停止,再通过right和left来确定长度
int left=0,right=s.size()-1;
while(left<right)
{
if(s[left]!=s[right]) break; //左右两边字符不同break,进行返回
char same=s[left];
//一下循环条件应该更改为left<=right解决出现s执行没有字符的情况
while(left<=right&&same==s[right]) right--; //出右边相同字符
while(left<=right&&same==s[left]) left++; //出左边相同字符
}
return right-left+1;
}
};
2105. 给植物浇水 II
题解:让左右两边同时进行浇水,当水不足的时候补水;注意:相遇位置谁来浇水。
class Solution {
public:
int minimumRefill(vector<int>& plants, int capacityA, int capacityB) {
//依旧是使用相向双指针进行实现
int n=plants.size();
int left=0,right=n-1;
int ret=0;
int ahave=capacityA,bhave=capacityB;
while(left<right)
{
if(ahave<plants[left]) //A浇水
{
ahave=capacityA;
ret++;
}
ahave-=plants[left++];
if(bhave<plants[right]) //B浇水
{
bhave=capacityB;
ret++;
}
bhave-=plants[right--];
}
if(n%2==1&&max(ahave,bhave)<plants[left]) ret++; //对于奇数个植物需要对相交位置进行判断
return ret;
}
};
977. 有序数组的平方
题解:对负数和整数进行拆分,以cur1为分界线,则[0,cur1]内的平方是呈现递减的趋势,[cur1+1,n-1]内的平方是呈现递增的趋势。就转化为了合并两个有序数组。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n=nums.size();
int cur1=0;
while(cur1<n&&nums[cur1]<0) cur1++;
cur1--; //cur1向前走 从[0,cur1]的平方是降序的
int cur2=cur1+1; //cur2向后走 从[cur2,n-1]的平方是升序的
vector<int> ret(n);
int k=0;
while(cur1>=0&&cur2<n)
{
if(abs(nums[cur1])<abs(nums[cur2])) ret[k++]=nums[cur1]*nums[cur1--];
else ret[k++]=nums[cur2]*nums[cur2++];
}
while(cur1>=0) ret[k++]=nums[cur1]*nums[cur1--]; //判断cur1是否走完了
while(cur2<n) ret[k++]=nums[cur2]*nums[cur2++];
return ret;
}
};
658. 找到 K 个最接近的元素
题解:先找到最接近x的位置,然后再从该位置分别向前,向后取元素。最终确定区间的位置。
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
//先找到数组中最接近x的位置
int n=arr.size();
int pos=0;int near=INT_MAX; //用pos位置来记录最接近x的位置
for(int i=0;i<n;i++)
{
if(near>abs(arr[i]-x))
{
near=abs(arr[i]-x);
pos=i;
}
}
//此时的pos位置就是最接近x的位置
int cur1=pos,cur2=pos+1; //将cur1向前走,cur2向后走,进行一次判断去哪一边的元素
while(k&&cur1>=0&&cur2<n)
{
int left=abs(arr[cur1]-x);
int right=abs(arr[cur2]-x);
if(left>right) cur2++; //取右边的
else cur1--; //取左边的
k--;
}
while(k&&cur1>=0) cur1--,k--; //防止k还没有完
while(k&&cur2<n) cur2++,k--;
vector<int> ret(arr.begin()+cur1+1,arr.begin()+cur2);
return ret;
}
};
1471. 数组中的 k 个最强值
题解:先对数组进行排序找到中间值。因为数组是排序好的,所以左右两边的值是距离中间值最远的,也就是最有可能成为最强值的数,再对左右进行比较确定最强值。
class Solution {
public:
vector<int> getStrongest(vector<int>& arr, int k) {
int n=arr.size();
sort(arr.begin(),arr.end()); //先进行排序找中间值
int m=arr[(n-1)/2];
//左右两边一定比中间强,所以控制[left,right]区间,从左右两边找最强值
vector<int> ret;
int left=0,right=n-1;
while(k&&left<=right)
{
int lnum=abs(arr[left]-m);
int rnum=abs(arr[right]-m);
if(lnum>rnum) //左边大,入左边
{
ret.push_back(arr[left]);
left++;
}
else //右边大,入右边
{
ret.push_back(arr[right]);
right--;
}
k--;
}
return ret;
}
};
167. 两数之和 II - 输入有序数组
题解:标准的同向双指针的题目,通过左右两边的最大值和最小值来进行缩小和扩大范围直到找到合适的下标组合。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
//使用相向双指针来实现,当左右两边之和小的时候就让left++
//当左右两数之和大的时候让right--
//相等的时候就返回
int left=0,right=numbers.size()-1;
while(left<right)
{
if(numbers[left]+numbers[right]==target) return {left+1,right+1}; //注意题目中下标是从1开始的
else if(numbers[left]+numbers[right]>target) right--;
else left++;
}
return {};
}
};
633. 平方数之和
题解:b一定是小于等于sqrt(c)的,所以可以直接确定左右区间使用相向双指针来找合适的a和b即可。
class Solution {
public:
bool judgeSquareSum(int c) {
int left=0,right=sqrt(c); //right一定是小于等于c的算数平方根的
//所以确定左右边界,通过左右两边的平方和确定区间的移动
while(left<=right)
{
long long the=left*left;
long long other=right*right;
if(the+other==c) return true;
else if(the+other>c) right--;
else left++;
}
return false;
}
};
2824. 统计和小于目标的下标对数目
方法一:对数组进行排序,right从后向前,依次找nums[right]组合满足条件的数目,将这些数进行相加即可;满足nums[right]+nums[left]>=target的最小left就是nums[right]可以组合的个数。
class Solution {
public:
int countPairs(vector<int>& nums, int target) {
sort(nums.begin(),nums.end());
int ret=0,n=nums.size();
for(int i=n-1;i>0;i--)
{
//使用islower_bound来进行临界条件的查找
int r=lower_bound(nums.begin(),nums.begin()+i,target-nums[i])-nums.begin();
if(r!=n) ret+=r;
}
return ret;
}
};
方法二:将数组进行排序后,使用相向双指针进行统计;当nums[left]+nums[right]<target的时候nums[left]就可以与[left+1,right]区间的所有数进行配对,ret+=right-left,当前left就完成了对所有数的匹配,left++;当nums[left]+nums[right]>=target时,nums[right]就不能与[left,right]的所有数进行匹配,当前的right就完成了对所有数的匹配,right--。
class Solution {
public:
int countPairs(vector<int>& nums, int target) {
sort(nums.begin(),nums.end());
int left=0,right=nums.size()-1;
int ret=0;
while(left<right)
{
if(nums[left]+nums[right]<target)
{
ret+=right-left;
left++;
}
else
right--;
}
return ret;
}
};
2563. 统计公平数对的数目
题解:与上一题的方法二相似也是使用有序的性质将数对的选择进行剪枝;使用相向双指针进行,当nums[left]+nums[right]>upper时说明此时的right没有与之匹配的left了,将right--;当nums[left]+nums[right]<lower时说明此时的left没有与之匹配的right了,将left++;当在有效区间以内的时候,可以固定right求left满足的个数,也可以固定left求right满足条件的个数。
class Solution {
public:
long long countFairPairs(vector<int>& nums, int lower, int upper) {
int n=nums.size();
sort(nums.begin(),nums.end()); //先进行排序,此时的left和right有序方便对不需要组合进行剪枝
int left=0,right=n-1;
long long ans=0;
while(left<right)
{
if(nums[left]+nums[right]>upper) right--; //当left+right对应的值大于upper时right就没有匹配的left了
else if(nums[left]+nums[right]<lower) left++; //当left+right对应的值小于lower时left就没有匹配的right了
else
{
//此时的nums[left]+nums[right]是满足题意的,所以可以固定right,将找第一个left不满足条件的位置
int end=upper_bound(nums.begin()+left,nums.begin()+right,upper-nums[right])-nums.begin()
-left;
ans+=end;
right--;
}
}
return ans;
}
};
LCP 28. 采购方案
题解:与两题一样,还是通过有序的数组对进行剪枝。
class Solution {
public:
int purchasePlans(vector<int>& nums, int target) {
int n=nums.size();
sort(nums.begin(),nums.end()); //先进行排序
int left=0,right=n-1;
int ret=0;
while(left<right)
{
if(nums[left]+nums[right]>target) right--; //此时right的值不能与[left,right-1]任意一个值进行配对
else
{
ret=(ret+right-left)%1000000007;
left++;
}
}
return ret;
}
};
15. 三数之和
题解:将三数求和------->两数求和,枚举数组的每一个位置找另外两个数即可。找另外两个数依旧是使用相向双指针进行。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n=nums.size();
sort(nums.begin(),nums.end()); //进行排序
vector<vector<int>> ret;
if(nums[0]>0||nums[n-1]<0) return ret; //当最大值<0以及最小值>0不可能存在满足的数组
for(int i=0;i<n-2;i++)
{
if(i>0&&nums[i]==nums[i-1]) continue; //当前数与上一个就不需要对当前数进行处理,
//否则三元组会重复
if(nums[i]+nums[i+1]+nums[i+2]>0) break; //当最小的三个数之和>0没有三元组了
if(nums[i]+nums[n-1]+nums[n-2]<0) continue; //当最小的数与最大的两个数相加<0,当前的i与任何数都不能进行匹配
int left=i+1,right=n-1; //进行两数之和
while(left<right)
{
if(nums[left]+nums[right]+nums[i]>0) right--;
else if(nums[left]+nums[right]+nums[i]<0) left++;
else
{
ret.push_back({nums[left],nums[right],nums[i]});
left++;
while(left<right&&nums[left]==nums[left-1]) left++; //跳过重复的数据
right--;
while(left<right&&nums[right]==nums[right+1]) right--;
}
}
}
return ret;
}
};
16. 最接近的三数之和
题解:此题与上一题一样,依旧是枚举每一个元素进行三数之和求最接近的位置即可。
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
//与三数之和类似,还是枚举每一个位置,用同向双指针进行查找最接近的位置
int n=nums.size();
sort(nums.begin(),nums.end());
long long ret=INT_MAX;
for(int i=0;i<n-2;i++)
{
//进行同向双指针让和靠近target
int left=i+1,right=n-1;
while(left<right)
{
int differ=nums[i]+nums[left]+nums[right]; //求此时的三数之和
if(abs(differ-target)<abs(ret-target)) ret=differ; //判断时候需要进行更新答案
if(nums[i]+nums[left]+nums[right]>target) right--; //此时三数>target,让right--使其靠近target
else if(nums[i]+nums[left]+nums[right]<target) left++; //此时三数<target,让left++使其靠近target
else return target; //相等就是最接近的位置,直接进行返回
}
}
return ret;
}
};
18. 四数之和
题解:与三数求和类似,将四数求和----->三数求和----->两数求和。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
//将四数之和----->三数之和------>两数之和
int n=nums.size();
sort(nums.begin(),nums.end());
vector<vector<int>> ret;
for(int i=0;i<n-3;i++)
{
if(i&&nums[i]==nums[i-1]) continue; //防止相同数据进行重复插入
for(int j=i+1;j<n;j++)
{
if(j!=i+1&&nums[j]==nums[j-1]) continue; //防止相同数据进行重复插入
int left=j+1,right=n-1;
while(left<right)
{
//此处的四个数进行相加可能越界,将int强转为long long防止越界
if((long long)nums[i]+nums[j]+nums[left]+nums[right]>target) right--;
else if((long long)nums[i]+nums[j]+nums[left]+nums[right]<target) left++;
else
{
ret.push_back({nums[i],nums[j],nums[left],nums[right]});
left++;
while(left<right&&nums[left]==nums[left-1]) left++;
right--;
while(left<right&&nums[right]==nums[right+1]) right--;
}
}
}
}
return ret;
}
};
611. 有效三角形的个数
题解:与三数求和类似,此题可以通过先确定一边(最大值或最小值),再通过同向双指针找能够组成三角形的组合即可。当nums[min]+nums[mid]>nums[max]时就一定能够组成三角形。
class Solution {
public:
int triangleNumber(vector<int>& nums) {
//类似与三数求和,通过固定左右一边,再通过同向双指针找合适的位置
int n=nums.size(),ret=0;
sort(nums.begin(),nums.end());
for(int i=n-1;i>=0;i--) //固定右边,找[0,i-1]中合适的位置
{
int left=0,right=i-1;
while(left<right)
{
if(nums[right]+nums[left]<=nums[i]) left++;
else //此时的nums[left],nums[right],nums[i]是满足条件的
{ //区间[left,right-1]与nums[right],num[i]都可以组成三角型
ret+=right-left;
right--;
}
}
}
return ret;
}
};
1577. 数的平方等于两数乘积的方法数
题解:类似于两数求和,分别遍历nums1和nums2,在另一个数组中使用同向求和来找合适的位置。
class Solution {
int ret;
void twosum(vector<int>& nums,long long target)
{
int n=nums.size();
int left=0,right=n-1;
while(left<right)
{
if((long long)nums[left]*nums[right]>target) right--;
else if((long long)nums[left]*nums[right]<target) left++;
else
{
int l=1,tmp=left+1;
while(tmp<right&&nums[tmp]==nums[tmp-1]) l++,tmp++; //计算left有多少个相同的数
ret+=l;
right--;
}
}
}
public:
int numTriplets(vector<int>& nums1, vector<int>& nums2) {
//依旧是两数之和题型
//遍历nums1,将nums[i]^2作为target在nums2中找满足的个数
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int n1=nums1.size(),n2=nums2.size();
ret=0;
for(int i=0;i<n1;i++)
twosum(nums2,(long long)nums1[i]*nums1[i]); //进行两数求和
for(int i=0;i<n2;i++)
twosum(nums1,(long long)nums2[i]*nums2[i]);
return ret;
}
};
923. 三数之和的多种可能
题解:此题时三数之和的变形题,依旧采用遍历数组,再通过相向双指针进行统计合适的个数。但是要注意的是此题时允许右重复组合出现的,所以可以先对数组各个元素的个数进行统计。
class Solution {
public:
int threeSumMulti(vector<int>& nums, int target) {
//三数求和变形题
unordered_map<int,int> mm;
for(auto e:nums) mm[e]++; //记录每个数据的个数
int n=nums.size();
sort(nums.begin(),nums.end());
int ret=0;
for(int i=0;i<n;i++) //遍历数组
{
//进行两数求和
if(--mm[nums[i]]==0) mm.erase(mm[nums[i]]);
int left=i+1,right=n-1;
while(left<right)
{
if(nums[left]+nums[right]+nums[i]>target) right--;
else if(nums[left]+nums[right]+nums[i]<target) left++;
else
{
//防止left与right的值相等造成的重复计数
if(nums[left]==nums[right]) ret=(ret+(mm[nums[right]]-1)*mm[nums[right]]/2)%1000000007;
else ret=(ret+mm[nums[right]]*mm[nums[left]])%1000000007;
right--,left++;
while(left<right&&nums[left]==nums[left-1]) left++; //取出重复元素
while(left<right&&nums[right]==nums[right+1]) right--; //取出重复元素
}
}
}
return ret;
}
};
948. 令牌放置
题解:此题的关键在于如何将利益最大化,根据题意可以看出用已有得分获得最大能量,再用已有能量买最小令牌获取得分即可实现利益最大化。
class Solution {
public:
int bagOfTokensScore(vector<int>& tokens, int power) {
//当能量不够时,保证每次都使用1分都获取最大的能量
//当能量充足的时候,使用能量换取最小的令牌来获得1分,这样就可以使得利益最大化
int n=tokens.size();
sort(tokens.begin(),tokens.end()); //先进行排序
int left=0,right=n-1;
int ret=0;
while(left<right)
{
if(power<tokens[left]) //能量不够,用得分获得最多能量
{
if(ret==0) return 0;
ret--;
power+=tokens[right--];
}
power-=tokens[left++]; //用最小能量获取得分
ret++;
}
if(left==right&&power>=tokens[left]) ret++; //判断相等位置是否还可以获得得分
return ret;
}
};
11. 盛最多水的容器
题解:经典的最大蓄水池问题,采用同向双指针通过每次循环确定一边的最大值即可。
class Solution {
public:
int maxArea(vector<int>& height) {
//通过左右两边双指针可以直接确定一个挡板可以存储的最多水量
//eg:示例1,[0,n-1]此时的0比nums[n-1]小,所以选取0作为一个挡板可以存储的最多水量是[0,n-1]
//此时0的最大储水量已经确定了,让0--->1继续向后找;此时nums[n-1]比nums[1]小,
//所以以n-1作为一个挡板最大储水量是[1,n-1]此时n-1位置已经确定了,可以让n-1--->n-2
//通过同向双指针就可以实现找到最大容量的效果
int left=0,right=height.size()-1;
int ret=0;
while(left<right)
{
if(height[left]>height[right])
{
ret=max(ret,height[right]*(right-left));
right--;
}
else
{
ret=max(ret,height[left]*(right-left));
left++;
}
}
return ret;
}
};
42. 接雨水
解答一:同向双指针,与“盛最大水的容器”类似,此题依旧是让柱子矮的一边进行移动,但是此时每一次循环确定的是一个位置可以存储的水量,为了获取该位置的水量还需要分别存储左右两边的最长柱子。
class Solution {
public:
int trap(vector<int>& height) {
//此题可以使用双指针进行实现,依旧移动柱子矮的一边,但是在每一次只是将一列进行注水
//并且需要存储左右两边最长的柱子
int left=0,right=height.size()-1;
int left_max=0,right_max=0;
int ret=0;
while(left<right)
{
if(height[left]<height[right])
{
left_max=max(left_max,height[left]);
ret+=left_max-height[left++];
}
else
{
right_max=max(right_max,height[right]);
ret+=right_max-height[right--];
}
}
return ret;
}
};
解法二:使用前缀和进行实现;假设右边柱子无穷高,从左向右来确定每个位置可以存储的最大水量;再假设左边柱子无穷高,从右向左来确定每个位置可以存储的最大水量;再取每一个位置的最小值即可得出该位置的真实存水量。
class Solution {
public:
int trap(vector<int>& height) {
int n=height.size();
vector<int> left_area(n);
vector<int> right_area(n);
int left_max=0,right_max=0;
for(int i=0;i<n;i++) //求从左向右的储水量
{
left_max=max(left_max,height[i]);
left_area[i]=left_max-height[i];
}
for(int i=n-1;i>=0;i--) //求从右向左的储水量
{
right_max=max(right_max,height[i]);
right_area[i]=right_max-height[i];
}
int ret=0;
for(int i=0;i<n;i++) //取两个储水量的较小值即可
ret+=min(right_area[i],left_area[i]);
return ret;
}
};
1616. 分割两个字符串得到回文串
题解:使用同向双指针找到左右两边第一个不对称的位置,以该位置作为基点判断能否形成回文字符串即可。
class Solution {
public:
bool same(string& s) //判断是否是回文字符串
{
int left=0,right=s.size()-1;
while(left<right)
if(s[left++]!=s[right--]) return false;
return true;
}
bool checkP(string prev,string next)
{
//通过同向双指针找第一个不相同的位置,以该位置作为基点将左右两边进行拼接,判断拼接后是否形成回文字符串
int n=prev.size();
int left=0,right=n-1;
while(left<right)
{
if(prev[left]!=next[right])
{
//[0,left)和[left,n)
string tmp1=prev.substr(0,left);
tmp1+=next.substr(left,n-left);
//[0,right]和(right,n)
string tmp2=prev.substr(0,right+1);
tmp2+=next.substr(right+1,n-right-1);
return same(tmp1)||same(tmp2);
}
left++,right--;
}
return true;
}
bool checkPalindromeFormation(string a, string b) {
return checkP(a,b)||checkP(b,a);
}
};
同向双指针
与相向双指针相反,两个指针都向左或者都向右。
611. 有效三角形的个数
题解:关于该题前面在实现的时候使用了:固定最大边+相向双指针进行实现,此处可以使用固定最小边+同向双指针进行实现。
class Solution {
public:
int triangleNumber(vector<int>& nums) {
//此题可以采用固定最小边a,求另外两条边b,c;需要满足c-b<a
//所以b和c之间的距离不能太大,所以可以使用同向双指针
int n=nums.size(),ret=0;
sort(nums.begin(),nums.end());
for(int i=0;i<n-2;i++)
{
int a=nums[i];
if(a==0) continue; //对于长度为0的边不能参与计数
//进行同向双指针
int count=0,left=i+1;
for(int right=i+2;right<n;right++)
{
while(left<right&&nums[right]-nums[left]>=a) left++;
count+=right-left;
}
ret+=count;
}
return ret;
}
};
此处同向双指针与相向双指针类似就不过多举例了。
背向双指针
两个指针从数组中的同一个位置出发,一个向左,另一个向右,背向移动。
1793. 好子数组的最大分数
题解:从k位置使用两个指针分别向左右两边进行扩展;在扩展的时候每次向外扩1,所以要考虑是向左还是向右进行扩展,此处为了保证区间中的最小值尽可能的大,选取左右两边的较大值进行扩展。
class Solution {
public:
int maximumScore(vector<int>& nums, int k) {
//使用双指针让cur1和cur2分别从k位置向左,向右走
//向左右两边扩展的时候尽量选择较大的值,来保证min的结果不会太小
int n=nums.size();
int cur1=k-1,cur2=k+1;
int ret=nums[k],Min=nums[k];
while(cur1>=0&&cur2<n)
{
if(nums[cur1]>nums[cur2]) //当cur1的值大于cur2的时候向cur1那边扩
{
Min=min(Min,nums[cur1]);
ret=max(ret,Min*(cur2-cur1));
cur1--;
}
else //当cur2的值大于cur1的时候向cur2那边扩
{
Min=min(Min,nums[cur2]);
ret=max(ret,Min*(cur2-cur1));
cur2++;
}
}
while(cur1>=0) //解决有一边还没有扩完的情况
{
Min=min(Min,nums[cur1]);
ret=max(ret,Min*(cur2-cur1));
cur1--;
}
while(cur2<n)
{
Min=min(Min,nums[cur2]);
ret=max(ret,Min*(cur2-cur1));
cur2++;
}
return ret;
}
};
原地修改
原地修改类题型:对数组进行修改使得修改后的数组满足题意;原地修改的方法不固定可能会使用相向双指针,同向双指针也可能会使用背向双指针。
27. 移除元素
题解:可以使用同向双指针来实现,使用prev,cur来记录位置,cur在前面走,当nums[cur]!=val时将cur的值赋给prev,否则让cur++即可。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n=nums.size();
int prev=0,cur=0;
while(cur<n)
{
if(nums[cur]!=val)
nums[prev++]=nums[cur];
cur++;
}
return prev;
}
};
26. 删除有序数组中的重复项
题解:使用同向双指针prev,cur解决,cur在前面走,当cur对应的位置满足条件就将cur的元素赋值给prev即可;使用map记录cur前已经出现过的数字。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
//使用双指针prev,cur,cur向前走当cur位置的元素满足条件时将cur的元素赋值给prev即可
//使用map来记录cur前面已经存在的元素
unordered_map<int ,int> mm;
int prev=0,n=nums.size();
for(int cur=0;cur<n;cur++)
{
if(mm.count(nums[cur])==0)
{
mm[nums[cur]]++;
nums[prev++]=nums[cur];
}
}
return prev;
}
};
80. 删除有序数组中的重复项 II
题解:与上一题一样,只需要将cur赋值给prev的条件进行修改即可。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
//使用双指针prev,cur,cur向前走当cur位置的元素满足条件时将cur的元素赋值给prev即可
//使用map来记录cur前面已经存在的元素
unordered_map<int ,int> mm;
int prev=0,n=nums.size();
for(int cur=0;cur<n;cur++)
{
if(mm[nums[cur]]<2)
{
mm[nums[cur]]++;
nums[prev++]=nums[cur];
}
}
return prev;
}
};
283. 移动零
题解:将数组从的0移动到后面----->将数组中的非零移动到前面即可。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
//将0移动到末尾------->将非零移动到数组前面
int prev=0,n=nums.size();
for(int cur=0;cur<n;cur++)
{
if(nums[cur]!=0) nums[prev++]=nums[cur];
}
//此时非0全部移动到前面了
//将prev后面的元素全部置为0即可
while(prev<n) nums[prev++]=0;
}
};
905. 按奇偶排序数组
题解:将偶数放到数组前面,将奇数放到数组后面;可以使用相向双指针left,right;让left向右找奇数,让right向左找偶数,找到后将left和right进行交换即可。
class Solution {
public:
vector<int> sortArrayByParity(vector<int>& nums) {
//将偶数放在前面,将奇数放在后面
//使用相向双指针进行实现,从让left向右走找奇数;让right向左走找偶数
int left=0,right=nums.size()-1;
while(left<right)
{
while(left<right&&nums[left]%2==0) left++; //找奇数
while(left<right&&nums[right]%2==1) right--; //找偶数
swap(nums[left],nums[right]); //将奇偶进行交换
}
return nums;
}
};
922. 按奇偶排序数组 II
题解:与上一题类似;根据题目可以分析出数组元素个数是偶数个,并且奇数位放奇数,偶数位放偶数即可。所以依旧是使用同向双指针left,right,但是left和right每次移动两步。
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
//将偶数放在前面,将奇数放在后面
//使用相向双指针进行实现,从让left向右走找奇数;让right向左走找偶数
int n=nums.size();
int left=0,right=n-1;
while(left<n&&right>0)
{
while(left<n&&nums[left]%2==0) left+=2; //找奇数
while(right>0&&nums[right]%2==1) right-=2; //找偶数
if(left<n&&right>0)
swap(nums[left],nums[right]); //将奇偶进行交换
}
return nums;
}
};
3467. 将数组按照奇偶性转化
题解:先遍历数组将数组中的奇数置为1,将数组中的偶数置为0;再将数组中的0放到数组前面即可。
class Solution {
public:
vector<int> transformArray(vector<int>& nums) {
//将数组中的奇数-->1,偶数---->0;再将数组中的0放到数组前面
for(auto& e:nums)
e=e%2==0?0:1;
int prev=0,n=nums.size();
for(int cur=0;cur<n;cur++)
if(nums[cur]==0) nums[prev++]=nums[cur];
while(prev<n) nums[prev++]=1;
return nums;
}
};
2460. 对数组执行操作
题解:模拟+同向双指针;
class Solution {
public:
vector<int> applyOperations(vector<int>& nums) {
int n=nums.size();
for(int i=0;i<n-1;i++)
{
if(nums[i]==nums[i+1])
{
nums[i]*=2;
nums[i+1]=0;
}
}
//将非0放到数组前面
int prev=0;
for(int cur=0;cur<n;cur++)
{
if(nums[cur]!=0) nums[prev++]=nums[cur];
}
while(prev<n) nums[prev++]=0;
return nums;
}
};
1089. 复写零
题解:模拟+同向双指针;先模拟规则找到最后一个移动的元素,再从后往前进行复写。
class Solution {
public:
void duplicateZeros(vector<int>& nums) {
//先通过模拟找到返回数组中的最后一位
int n=nums.size();
int prev=0,cur=0;
while(cur<n)
{
if(nums[prev]==0) cur++;
cur++,prev++;
}
//此时prev-1就是输出数组的最后一位
//从prev开始向前移动
prev-=1;
//注意:有一种特殊情况,当最后一位是0的时候进行向后移只剩一个数没有覆盖,所以只有一个0,没有复写
if(cur==n+1) nums[n-1]=0,cur=n-2,prev-=1; //进行特殊处理,将末尾没有复写的0直接加到结尾即可
else cur=n-1;
for(;prev>=0;prev--)
{
nums[cur--]=nums[prev];
if(nums[prev]==0) nums[cur--]=0; //进行复写
}
}
};
总结
关于双指针的题目关键在于如何理解题目,两个指针在何时才能向前或向后移动;滑动窗口其实也是双指针的以内,其也属于同向双指针。题目讲解到此就结束了,感谢阅读,让我们在下一篇算法中再见。。