Bootstrap

算法 —— 双指针

目录

移动零 

复写零

快乐数

盛最多水的容器

有效三角形的个数

查找总价格为目标值的两个商品

三数之和

四数之和


移动零 

 下图以样例1为例,看下图如何做到保证非零元素相对顺序前提下,移动零元素。

 代码实现如下:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        for (int cur = 0, dest = -1; cur < nums.size(); cur++)
            if (nums[cur])//非0就交换
                swap(nums[++dest], nums[cur]);
    }
};

复写零

注意边界情况:例如 [ 1 0 2 3 0 4 ] dest会越界访问,此时需要把cur和dest回归到上一步位置

代码实现如下:

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        // 1.找到最后一个复写的数
        int cur = 0, dest = -1, n = arr.size();
        while (dest < n)
        {
            if (arr[cur])
                dest++;
            else
                dest += 2;
            if (dest >= n - 1)
                break;
            cur++;
        }

        // 2.处理边界情况
        if (dest == n)
        {
            cur--; dest -= 2;
            arr[n - 1] = 0;
        }

        // 3.从后往前遍历完成复写
        while (cur >= 0)
        {
            if (arr[cur])
                arr[dest--] = arr[cur--];
            else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
    }
};

快乐数

先看示例2为什么会输出false:

此题目一共三种情况:1、进入全是1的环     2、进入全部不是1的环     3、无限延伸下去不带环

但是题目告诉我们只有两种情况,为什么不会无限延伸下去,可以用鸽巢原理解释:

鸽巢原理: 把(n+1)个物体任意放进n个鸽巢中(n是非0自然数),一定有一个鸽巢中至少放进了2个物体。 

题目给出范围n最大为2 ^ 31 - 1,即 2.1 * 10 ^ 9,假设是9999999999,那么经过一次平方和后变成810,也就是说n最大值经过一次平方和后最大不超过810,最小不低于1。

那么把810看作巢穴,把一次平方和规则看作鸽,经过811次平方和后,会导致810个巢穴不够分,至少有一个巢穴是两个鸽:意味着经过810次以上的平方和规则后,势必出现重复,重复后就进环。

代码入下:

class Solution {
public:
    // 计算每个位置上的数字的平方和
    int summary(int n)
    {
        int sum = 0;
        while (n)
        {
            int tmp = n % 10;
            sum += tmp * tmp;
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        int slow = n, fast = summary(n);
        // 快指针走两步  慢指针走一步
        while (slow != fast)
        {
            slow = summary(slow);
            fast = summary(summary(fast));
        }
        return slow == 1;
    }
};

盛最多水的容器

 设置两个左右指针,由于下标相减所得宽度是不断减小的,那么在有限长度内,保证宽度最大的前提下得到最大的长度

宽度变小,长度可能不变,也可能变小,根据上图得出以下代码:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0, right = height.size() - 1, area = 0;
        while (left < right)
        {
            int lenth = right - left;
            int width = min(height[left], height[right]);
            area = max(area, width * lenth);
            if (height[left] > height[right])
                right--;
            else
                left++;
        }
        return area;
    }
};

有效三角形的个数

 首先先对数组进行排序,例如 a , b , c 现在是升序状态,两个比c小的数相加大于c,那么c 加两个小数中的任何一个小数都比另外一个小数大。此外暴力枚举的时间复杂度过大,本题不适合。

首先固定最大的数,以一个循环为例,先固定最大数10,左指针指向下标为0的元素(2),右指针指向下标为5的元素(9),a就是2,b就是9,c就是10,a + b > c,那么a——b这个区间里所有数加b都大于c,个数即为right - left。一轮过后,right--找到次小的数5,再使left从下标0开始,a + b小于c,那么让left++,使得left变大,如果left == right时还小于最大数10,说明剩下的数无法组合成三角形,接着固定9作为最大数c,循环上述步骤。

代码如下:

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        // 1.优化排序
        sort(nums.begin(), nums.end());

        // 2.利用双指针解决问题
        int count = 0;
        for (int i = nums.size() - 1; i >= 2; i--)
        {
            int left = 0, right = i - 1;
            while(left < right)
            {
                if (nums[left] + nums[right] > nums[i])
                {
                    count += (right - left);
                    right--;
                }
                else
                    left++;
            }
        }
        return count;
    }
};

查找总价格为目标值的两个商品

首先最重要一点:题目告诉我们此数组为升序,那么可以利用单调性来解决问题。 

左指针右移相当于做加法操作,因为指向了一个更大的数,同理右指针左移相当于做减法操作,因为指向了一个更小的数。代码实现如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        vector<int> ret;
        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
            {
                ret.push_back(price[left]);
                ret.push_back(price[right]);
                break;
            }
        }
        return ret;
    }
};

三数之和

 此题最关键的为去重问题和避免越界问题,那么如何避免这两个问题发生呢?

首先要保证left指针始终在right左边,然后left++或者right--之后才可以和left前一个数或者right后一个数比较,并且 i 始终小于该数组元素个数。

代码实现如下:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ret;

        // 1.排序
        sort(nums.begin(), nums.end());

        // 2.利用双指针解决问题
        for (int i = 0; i < nums.size() - 2;)
        {
            // 大于0意味着后面全是正数   不可能相加为0
            if (nums[i] > 0)
                break;
            int left = i + 1, right = nums.size() - 1, target = -nums[i];
            while (left < right)
            {
                vector<int> ans;
                int sum = nums[left] + nums[right];
                if (sum > target)
                {
                    right--;
                }
                else if (sum < target)
                {
                    left++;
                }
                else
                {
                    ans.push_back(nums[i]);
                    ans.push_back(nums[left]);
                    ans.push_back(nums[right]);
                    ret.push_back(ans);
                    left++; right--;

                    // 去重 left 和 right
                    while (left < right && nums[left] == nums[left - 1])
                        left++;
                    while (left < right && nums[right] == nums[right + 1])
                        right--;
                }
            }
            // 去重 i
            i++;
            while (i < nums.size() && nums[i] == nums[i - 1])
                i++;
        }
        return ret;
    }
};

四数之和

 此题唯一有问题的地方在于数据的大小可能会超过int类型的最大值,那么我们需要将他强制类型转换成long long类型的数据,这样便于后面的比较。其余部分和三数之和并无多大区别。

代码如下:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        // 1.排序
        sort(nums.begin(), nums.end());

        // 2.利用双指针解决问题
        vector<vector<int>> ret;

        // 解决特殊情况   
        // 1.1 特殊情况  元素个数小于4
        if (nums.size() < 4)
            return ret;

        for (int a = 0; a < nums.size() - 3;) // 固定数 a
        {
            for (int b = a + 1; b < nums.size() - 2;) // 固定数 b
            {        
                // 1.2 特殊情况  数据溢出
                long long my_target = (long long)target - nums[a] - nums[b];
                int left = b + 1, right = nums.size() - 1;
                while (left < right)
                {
                    int sum = nums[left] + nums[right];
                    if (sum > my_target)
                        right--;
                    else if (sum < my_target)
                        left++;
                    else
                    {
                        ret.push_back({ nums[a],nums[b],nums[left],nums[right] });
                        left++; right--;
                        // 去重 left 和 right
                        while (left < right && nums[left] == nums[left - 1])
                            left++;
                        while (left < right && nums[right] == nums[right + 1])
                            right--;
                    }
                }
                // 去重 b
                b++;
                while (b < nums.size() && nums[b] == nums[b - 1])
                    b++;
            }
            // 去重 a
            a++;
            while (a < nums.size() && nums[a] == nums[a - 1])
                a++;
        }
        return ret;
    }
};
;