【专题一】双指针

本博客梳理双指针算法
常见的双指针:对撞指针、快慢指针
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 对暴力求解的优化

  1. 如果能构成三角形,只需两个小的边加起来大于第三边即可
  2. 因此可以先将原数组排序,从小到大枚举三元组

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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值