本博客梳理双指针算法
常见的双指针:对撞指针、快慢指针
1.对撞指针:一般用于顺序结构中
2.快慢指针:用两个移动速度不同的指针在数组或者链表等序列结构上移动,对于处理环形链表或数组非常有用,其实对于出现循环往复情况的问题,都可以考虑使用快慢指针
3.针对存在单调性的场景,双指针可以用来大幅度优化暴力枚举
1. 移动零
核心思想:快排中将数组分块的思想——数组分两块

class Solution {
public:
void moveZeroes(vector<int>& nums) {
if(nums.size() == 1)
return;
int cur = 0;
int dest = -1;
while(cur < nums.size())
{
if(nums[cur] == 0)
++cur;
else
{
++dest;
swap(nums[dest], nums[cur]);
++cur;
}
}
}
};
2. 复写0

class Solution {
public:
void duplicateZeros(vector<int>& arr) {
//1.首先找到最后一个要复写的数,再后面的都不用管了
int cur = 0;
int dest = -1;
int n = arr.size();//解决arr.size()返回size_t带来的麻烦
while(cur < n)
{
if(arr[cur] == 0)
dest += 2;
else
++dest;
if(dest >= n - 1)
break;
++cur;
}
//此时cur已经指向了最后一个需要复写的数
//处理一下边界情况:如果dest == n,则说明cur指向了0,复写产生了越界
if(dest == n)
{
//此时理论上n - 1和n位置都要复写0,但n位置越界,要特殊处理一下
--dest;
arr[dest] = 0;
--dest;
--cur;
}
//2.从后向前完成复写操作
while(cur >= 0)
{
if(arr[cur] == 0)
{
arr[dest--] = 0;
arr[dest--] = 0;
}
else
arr[dest--] = arr[cur];
--cur;
}
}
};
3. 快乐数
3.1 快慢指针的特性
在一个圆圈中,快指针总会追上慢指针。针对本题,如果快慢指针相遇的位置值为1,则说明是快乐数;相遇的位置值不为1,则说明不是快乐数
3.2 证明一下:为什么一定会进入循环?
int的最大值为2147483647,直接找一个数9999999999,根据题意变化一次后最大值为810,之后这个数无论怎么操作,一定落在[1,810]之间。假设最坏情况是变化810次的值都不同,没产生循环,那么第811次的值必产生循环
3.3 本题解法

class Solution {
public:
int squared_sum(int n)//获取n的每一位,计算平方和
{
int sum = 0;
while(n != 0)
{
int ones = n % 10;
sum = sum + ones*ones;
n = n / 10;
}
return sum;
}
bool isHappy(int n) {
//转化为判断链表是否带环
//如果是快乐数,经历若干次变化之后一定会进入重复的一个环里面
int slow = n;
int fast = squared_sum(n);
while(slow != fast)
{
slow = squared_sum(slow);
fast = squared_sum(squared_sum(fast));
if(slow == fast)//最终必定成环,快指针一定追上慢指针
break;
}
if(slow == 1 && fast == 1)
return true;
else
return false;
}
};
4. 盛水最多的容器
核心思想:对撞指针,利用单调性,使用双指针优化枚举
4.1 解法一:暴力枚举
两层for循环,枚举左右边界,找最大值(会超时)
4.2 解法二:对撞指针
4.2.1 对暴力求解的优化
暴力枚举无脑把所有情况都列出来了,但有些情况是冗余的,没必要枚举
4.2.2 本题解法

class Solution {
public:
int maxArea(vector<int>& height) {
int left = 0;
int right = height.size() - 1;
int ret = 0;
while(left < right)
{
int h = min(height[left], height[right]);
int width = right - left;
int V = h * width;
ret = max(ret, V);
if(height[left] <= height[right])
left++;
else
right--;
}
return ret;
}
};
5. 有效三角形的个数
5.1 解法一:暴力枚举
三层for循环枚举所有的三元组,然后三次判断两边之和大于第三边。缺点:枚举冗余,大量低效枚举
5.2 解法二:排序+双指针
5.2.1 对暴力求解的优化
- 如果能构成三角形,只需两个小的边加起来大于第三边即可
- 因此可以先将原数组排序,从小到大枚举三元组
5.2.2 本题解法
既然对数组进行了排序,那就有了单调性,可以考虑双指针

class Solution {
public:
int triangleNumber(vector<int>& nums) {
//首先:如果a ≤ b ≤ c,那么只需a + b < c,必能构成三角形
//因此:如果整个数组有序,统计起来肯定更方便
//所以:利用单调性,配合双指针会优化暴力解法
sort(nums.begin(), nums.end());
int max = nums.size() - 1;
int ret = 0;
while(max >= 0)
{
int c = nums[max];
int left = 0, right = max - 1;
while(left <= right)
{
if(nums[left] + nums[right] > c)//说明可以构成三角形
{
//符合条件的三元组个数:right - left
ret += right - left;
right--;
}
else
left++;//无符合条件的三元组,只有left++才有希望出现
}
--max;
}
return ret;
}
};
6. 两数之和
6.1 解法一:暴力求解
两层for循环列出所有数字的组合,判断是否等于目标值(超时)
6.2 解法二:单调性+双指针
优化暴力枚举:题目中已经指出数组有序,因此可以用双指针优化

class Solution {
public:
vector<int> twoSum(vector<int>& price, int target) {
int left = 0, right = price.size() - 1;
while(left < right)
{
int sum = price[left] + price[right];
if(sum > target)
right--;
else if(sum < target)
left++;
else
return {price[left], price[right]};
}
return {-1, -1};
}
};
7. 三数之和
7.1 解法一:排序+暴力枚举+利用set去重
7.2 解法二:单调性+双指针

class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> vv;
sort(nums.begin(), nums.end());
//固定一个数a,后面用双指针,找nums[left] + nums[right] = -a
int i = 0;
while(i < nums.size())
{
int left = i + 1;
int right = nums.size() - 1;
int a = nums[i];//最左边固定的那个数
vector<int> v;
while(left < right)
{
if(nums[left] + nums[right] == -a)
{
v = {a, nums[left], nums[right]};
vv.push_back(v);
//缩小[left, right]继续找
//先处理left
++left;
while(left < right && nums[left] == nums[left - 1])//跳过重复元素
++left;
//再处理right
--right;
while(left < right && nums[right] == nums[right + 1])//跳过重复元素
--right;
}
else if(nums[left] + nums[right] > -a)
--right;
else
++left;
}
++i;
while(i < left && nums[i] == nums[i - 1])
i++;
}
return vv;
}
};
2411

被折叠的 条评论
为什么被折叠?



