双指针专题

前言(回顾一下):

Leetcode 283.移动零

思路:

        使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。

        注意到以下性质:

①左指针左边均为非零数;

②右指针左边直到左指针处均为零。

        因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int i = 0, j = 0;
        for(; i < nums.size(); i ++){
            // 维护j的位置(指向小于i的&&nums[i] == 0的位置)
            while(j < i && nums[j])     j ++;
            swap(nums[i], nums[j]);
        }
    }
};

Leetcode 11. 盛最多水的容器

暴力求解

        即枚举每一个可能的左右边界,比较出最大的那个矩形面积即可。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int res = 0, n = height.size();
        for(int i = 0; i < n; i ++){
            for(int j = i + 1; j < n; j ++){
                int h = min(height[i], height[j]);
                res = max(res, h * (j - i));
            }
        }
        return res;
    }
};    //1e5的数据范围,n^2过不了

双指针优化

i是左指针,j是右指针。由于面积受限于较短边,因此若移动较大高度的指针,那么区域的长度-1,高度的上限仍然是较短边的边长,即面积只会减少(本题的单调性体现)因此,可以优化掉这部分计算。(相向双指针)

class Solution {
public:
    int maxArea(vector<int>& height) {
        int res = 0, n = height.size();
        for(int i = 0, j = n - 1; i < j; ){
            res = max(res, (j - i) * min(height[i], height[j]));    // 计算
            // 移动指针
            if(height[i] > height[j])   j --;
            else                        i ++;
        }
        return res; // 时间:O(n) 空间:O(1)
    }
};

Leetcode 167.两数之和 II - 输入有序数组

简单来说和两数之和的区别在于,数组本身有序,对应唯一答案。

单调性体现:如果nums[i] + nums[j] > target,那么i++只会让数值更大,所以最后的解一定只存在于让j--(让nums[j]变小)的情况内。

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        vector<int> res;
        int n = numbers.size();
        int i = 0, j = n - 1;
        while (i < j){
            if(numbers[i] + numbers[j] == target){
                res.push_back(i + 1);   res.push_back(j + 1);
            }
            // 维护指针位置
            if(numbers[i] + numbers[j] > target)    j --;
            else    i ++;
        }
        return res;
    }
};

 Leetcode 15. 三数之和

暴力解法

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        unordered_set<string> hash;
        vector<vector<int>> res;
        int n = nums.size();
        sort(nums.begin(), nums.end());
        for(int i = 0; i < n; i ++)
            for(int j = i + 1; j < n; j ++)
                for(int k = j + 1; k < n; k ++){
                    if(nums[i] + nums[j] + nums[k] == 0){
                        string x = to_string(nums[i]) + "," + to_string(nums[j]) + "," + to_string(nums[k]);
                        if(!hash.count(x)){ // 没出现过
                            vector<int> t = {nums[i], nums[j], nums[k]};
                            res.push_back(t);
                            hash.insert(x);
                        }
                    }
                }
        return res;
    }
};

本题核心解决两个问题:

①如何去重?

②用双指针优化掉暴力解法(主指针i移动确定出target,j和k作为剩下位置的相向指针,转化成 Leetcode 167.两数之和 II - 输入有序数组

双指针+判重

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        int n = nums.size();
        sort(nums.begin(), nums.end());
        for(int i = 0; i < n - 2; i ++){    // 每次固定好i,剩下的j和k当做两数之和2即可
            int x = nums[i];
            if(x + nums[i + 1] + nums[i + 2] > 0)   break;  // 后面就不会有满足要求的条件了(优化①)
            if(x + nums[n - 2] + nums[n - 1] < 0)   continue;//x太小了,得大一点(优化②)
            if(i > 0 && x == nums[i - 1])   continue;   // 判重(i确定了target的取值=-x)
            int j = i + 1, k = n - 1;
            while (j < k){
                int s = nums[j] + nums[k];
                if(s == -x){
                    vector<int> t = {nums[i], nums[j], nums[k]};
                    res.push_back(t);
                }
                // 维护指针区间
                if (s < -x){
                    j ++;       
                    while(j < k && nums[j] == nums[j - 1])  j ++;   // 判重
                }else{
                    k --;
                    while(j < k && nums[k] == nums[k + 1])   k --;   // 判重
                }
            }
        }
        return res;
    }
};

Leetcode 42. 接雨水

方法一:前后缀分解

 抽象成木板水桶,能接多少水取决于左边木板高、右边木板高和黑色柱体的高度。即

                        min(pre, suf) - height[i]

class Solution {
public: // 方法一:  前后缀分解 时间O(n),空间O(n)
    int trap(vector<int>& height) {
        int n = height.size(), res = 0;
        vector<int> pre(n, 0);      // 后缀数组
        vector<int> suf(n, 0);      // 前缀数组     
        pre[0] = height[0];
        // 初始化前后缀数组
        for(int i = 1; i < n; i ++){
            pre[i] = max(pre[i - 1], height[i]);
        }
        suf[n - 1] = height[n - 1];
        for(int i = n - 2; i >= 0; i --){
            suf[i] = max(suf[i + 1], height[i]);
        }
        // 求解每一个桶能装多少水
        for(int i = 0; i < n; i ++){
            res += min(pre[i], suf[i]) - height[i];
        }
        return res;
    }
};

 方法二:相向双指针

思路:如果左边的前缀最大值比右边后缀最大值小,那么左边这个木桶的容量就是前缀最大值(单调性体现在:右边已经算出的后缀值在后面的计算过程中单调不减!!前缀值的计算也是单调不减),算完之后把它向右扩展。如果后缀最大值比前缀最大值小,那么右边这个木桶的容量就是后缀最大值,算完后把它向左扩展。

class Solution {
public: // 方法二:相向双指针   时间O(n),空间O(1)
    int trap(vector<int>& height) {
        int n = height.size(), res = 0;
        int l = 0, r = n - 1;
        int pre = 0, suf = 0;   // 前缀高度和后缀高度

        while (l < r){  // 谁小谁移动=>最后l==r的位置一定是最高的柱子
            pre = max(pre, height[l]);
            suf = max(suf, height[r]);      
            res = res + (pre < suf ? (pre - height[l ++]) : (suf - height[r --]));
        }
        return res;
    }
};

方法三:单调栈

方法一/二均假设每个位置上都有一个桶,相当于是逐一竖着计算。那么是否可以采用横着计算的方式?-->"填水泥"

总的来说,单调栈的思路就是“找上一个更大元素,在找的过程中填坑”

class Solution {
public:
    int trap(vector<int>& height) {
        deque<int> stack;      // 模拟stack
        int n = height.size(), res = 0;
        for(int i = 0; i < n; i ++){
            while(!stack.empty() && height[i] > height[stack.back()]){  // 需要填补
                int bottem_h = height[stack.back()];    stack.pop_back();   // 修补pop出的位置
                if(stack.empty())   break;  // 左边阶梯上升则无法接水
                int h = height[i];
                int left_h = height[stack.back()];
                int delta_width =  i - stack.back() - 1;
                res += (min(left_h, h) - bottem_h) * delta_width;
            }
            stack.push_back(i);
        }
        return res;
    }
};

Leetcode 189. 轮转数组

方法一:拷贝数组

时间和空间的消耗都是O(n)的

class Solution {
public:
    void rotate(vector<int>& nums, int k) {     // 通过引用返回答案
        int n = nums.size();
        vector<int> t(nums);
        for(int i = 0; i < n; i ++)
            nums[(i + k) % n] = t[i];
    }
};

方法二:原地作法,翻转

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        auto rev = [&](int l, int r){
            while(l < r)
                swap(nums[l ++], nums[r --]);
        };
        int n = nums.size();
        k %= n; // 轮转k次 等效于 轮转k%n 次
        rev(0, n - 1);
        rev(0, k - 1);
        rev(k, n - 1);
    }
};

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值