LeetCode题解之双指针算法(一)

269 阅读3分钟

1.双指针算法基础知识

在暴力算法中,往往有大量的重复计算,无用计算,可由两个指针合作带来的单调性优化

2.基本题目

剑指 Offer 57. 和为s的两个数字

class Solution {
public:
/*
1.暴力:ij两重循环o(n^2)从0开始遍历,但i不动时,j相当于很长一段都在找<target的值,
2.单调性:甚至[i]就算与最大数组合也偏小,不如直接一个从头一个从尾,如果偏小直接省略o(n)过程
*/
    vector<int> twoSum(vector<int>& nums, int target) {
        int n = nums.size();
        int i = 0, j = n - 1;
        while(i < j)
        {
            if(nums[i] + nums[j] == target) return {nums[i], nums[j]};
            else if(nums[i] + nums[j] > target) j --;
            else i ++;
        }
        return {};
    }
};

剑指 Offer 57 - II. 和为s的连续正数序列

class Solution {
public:
/*
1.暴力:i指向头,j遍历找尾巴,求一段和,超过就i++,j继续从i遍历
2.单调性:
    [i,j]段<sum时,j继续后移
    [i,j]段>sum时,说明不存在i开头的数组和==sum,需要i++;i++后的[i,j]和如果<sum,说明前面的[i,j-k]之类的都<sum,也就优化了暴力做法的j每次从i遍历!太机智了
    找到==sum就输出
*/
    vector<vector<int>> findContinuousSequence(int sum) {
        //不用暴力枚举i.j,由于i往后移动,在正整数序列中,j肯定往后移动的单调性,用双指针优化成o(n)
        vector<vector<int>> res;
        for(int i = 1,j = 1,s =1;i<=sum;i++)//注意是<=,是在i循环里面
        {
            while(s < sum) s += ++ j;//一次性加完,下一步就判断
            if(s==sum && j-i>0)//长度>1
            {
                vector<int> line;
                for(int k = i;k<=j;k++) line.push_back(k);
                res.push_back(line);
            }
            s -= i;//i移位的时候减去
        }
        return res;
    }
};

15. 三数之和

class Solution {
public:
/*
先排序,不重复的情况下枚举第一个数p1,在p1后面的部分使用双指针找p2p3
*/
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());//排序
        vector<vector<int>> ans;

        for(int p1 = 0;p1 < n;p1 ++)//枚举p1
        {
            if(p1 > 0 && nums[p1] == nums[p1 - 1]) continue;//跳过p1重复数
            
            int target = - nums[p1];
            int p3 = n - 1, p2 = p1 + 1;//开始设置双指针
            while(p2 < p3)
            {
                while(p2 > p1 + 1 && p2 < p3 && nums[p2] == nums[p2 - 1]) p2 ++;//跳过p2重复数
                if(p2 == p3) break;//上一步保证了p2<p3,但跳出时可能时p2==p3,需要避免在之后的判断中
                if(nums[p2] + nums[p3] == target) 
                {
                    ans.push_back({nums[p1], nums[p2], nums[p3]});
                    p2 ++,p3 --;
                }
                else if(nums[p2] + nums[p3] > target) p3 --;
                else p2 ++;
            }
        }
        return ans;
    }
};

16. 最接近的三数之和

class Solution {
public:
/*
先排序,不重复的情况下枚举第一个数p1,在p1后面的部分使用双指针找p2p3
*/
    int threeSumClosest(vector<int>& nums,int x) {
        int n = nums.size();
        sort(nums.begin(), nums.end());//排序
        int best = 1e4;

        for(int p1 = 0;p1 < n;p1 ++)//枚举p1
        {
            if(p1 > 0 && nums[p1] == nums[p1 - 1]) continue;//跳过p1重复数
            
            int target = x - nums[p1];
            int p3 = n - 1, p2 = p1 + 1;//开始设置双指针
            while(p2 < p3)
            {
                while(p2 > p1 + 1 && p2 < p3 && nums[p2] == nums[p2 - 1]) p2 ++;//跳过p2重复数
                if(p2 == p3) break;//上一步保证了p2<p3,但跳出时可能时p2==p3,需要避免在之后的判断中
                int s = nums[p2] + nums[p3];
                if(s == target) return x;
                if(abs(s - target) < abs(best)) best =  s - target;//比较一个全局res即可
                if(s > target) p3 --;
                else p2 ++;
            }
        }
        return x + best;
    }
};

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int n = nums.size();
        int i = 0, j = n - 1;
        while(i < j)
        {
            while(i < n && nums[i] % 2) i ++;//找到偶数停止
            while(j >= 0 && nums[j] % 2 == 0) j --;//找到奇数停止
            if(i < j) swap(nums[i], nums[j]);//i<j则交换
        }
        return nums;
    }
};

283. 移动零

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        //flag指向非零元素将被放的位置,由于保证非零元素相对顺序,所以都从前往后遍历,先遍历到的也放前面
        int flag = 0, j = 0;
        int n = nums.size();
        while(j < n)
        {
            if(nums[j] != 0) {swap(nums[flag], nums[j]); flag++}
            j ++;
        }
    }
};

75. 颜色分类

注意:i左边的值是扫描过的,p0左边又全是0,所以p0是1,i与p0交换后i是1,直接i++即可

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n = nums.size();
        int p0 = 0, p2 = n - 1;
        for(int i = 0; i < n && i <=p2; )
        {
            if(nums[i] == 0){swap(nums[i],nums[p0]); p0 ++; i ++;}//是0和p0交换,p0++,保证p0左边严格=0,
            else if(nums[i] == 2) {swap(nums[i], nums[p2]); p2 --;}//是2和p2交换,p2--,保证p2右边严格=2
            else i ++; 
        }
    }
};

11. 盛最多水的容器

class Solution {
public:
    int maxArea(vector<int>& height) {
        //双指针算法
        //左右指针先指向左右端点ab,则容积=min(a,b)*t,若左小,则下一个可能的地方必须移动左:证明如下
        //a<b,若移动b到c,新面积min(a,c)*(t-1),由于min(a,c)是<=a的,且t-1<t,所以肯定面积变小
        //所以必须移动小的那个,才有可能变大,移动也就表示在接下来寻找更大的面积时,a不可能再作为边界了(min是<=a的,t也减小,相当于排除o(n)的遍历情况
        int l = 0, r = height.size()-1;
        int res = 0;
        while(l<r)
        {
            int v = min(height[l], height[r]) * (r - l);
            res = max(v, res);
            if(height[l] <= height[r]) l ++;
            else r --;
        }
        return res;
    }
};

560. 和为K的子数组

hashmap优化的前缀和思想,不用单独建立pre前缀和数组

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int,int> mp;
        mp[0] = 1;
        int cnt = 0, pre = 0;
        for(auto x : nums)
        {
            pre += x;
            if(mp.find(pre-k) != mp.end()) cnt += mp[pre-k];
            mp[pre] ++;
        }
        return cnt;
    }
};

941. 有效的山脉数组

class Solution {
public:
//方法1:线性扫描:先找到最高点坐标,且不能是0或者n-1;再往山下找
//方法2:双指针:<3返回false;从两端往上爬,相遇且不为0||n-1则是true
    bool validMountainArray(vector<int>& A) {
        bool decrease = false;
        int n = A.size();
        if(n == 0 || n == 1 ) return false;
        if(A[1] < A[0] || A[n - 1] > A[n - 2]) return false;//单增或单减的情况
        for(int i = 1; i < A.size(); i ++)
        {
            if(decrease)//反向
            {
                if(A[i] < A[i - 1]) continue;
                else return false;
            }
            else//递增
            {
                if(A[i] > A[i - 1]) continue;
                else if(A[i] == A[i - 1]) return false;
                else decrease = !decrease;
            }
        } 
        return true;
    }
};
//双指针
class Solution {
public:
    bool validMountainArray(vector<int>& A) {
        int n = A.size();
        if(n < 3) return false;
        int i = 0, j = n - 1;
        while(i + 1 <= j && A[i + 1] > A[i]) i ++;
        while(j - 1 >= i && A[j - 1] > A[j]) j --;
        if(i == j && i != 0 && i != n - 1) return true;
        else return false;
        
    }
};

922. 按奇偶排序数组 II

class Solution {
public:
    vector<int> sortArrayByParityII(vector<int>& A) {
//偶奇偶奇排列即可
    int n = A.size();
    int i = 0, j = 1;
    for(int k = 0; k < n && i < n && j < n;)
    {
        if(A[k] % 2 == k % 2) {k ++; continue;}
        else if(A[k] % 2 == 0) {swap(A[k], A[i]); i += 2;}
        else if(A[k] % 2 == 1) {swap(A[k], A[j]); j += 2;}
    }
    return A;
    }
};