【算法】优选 · 双指针

目录

移动零

复写零

快乐数

盛水最多的容器

有效的三角形个数

[和为 s 的两个数](#和为 s 的两个数)

三数之和

四数之和


移动零

https://leetcode.cn/problems/move-zeroes/

给定一个数组,编写一个函数将所有 0 移动到数组的末尾,保持非零元素的相对顺序,必须在原数组操作

**数组划分、数组分块:**在某种规则、标准下,把一个给定的数组,划分成若干区间
解决这类题首先想到 双指针算法(利用数组下标充当指针)

也可以解决快排中最核心的一步,但当相同元素很多时,逼近 N^2

2 个指针的作用:

cur:从左往右扫描数组,遍历数组

dest:已处理的区间内,非零元素的最后一个位置,所以刚开始定义为 -1

cur 从前往后遍例的过程中

  1. 遇到 0 元素:cur++;

  2. 遇到非 0 元素:swap(dest + 1, cur); dest++; cur++;

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

复写零

https://leetcode.cn/problems/duplicate-zeros/

输入 arr = [1,0,2,3,0,4,5,0] 输出 [1,0,0,2,3,0,0,4] 必须在原数组操作

操作数组中元素, 先想到 双指针算法

先根据"异地"操作,优化成双指针下的"就地"操作

++异地++ 在本题指++新开的数组++ 上操作:从前向后,cur 遇到 0,dest 写 2 遍;cur 遇到非 0,dest 写 1 遍

但就地在原数组操作时,会覆盖后面 cur 没有遍历的元素,因此要从前向后不行

正确操作:cur 指向最后复写的数,dest 指向最后位置,从后往前

dest:结果中最后需要复写的位置,刚开始并不知道在哪,所以定义为 -1

找最后复写的数

  1. 看 cur 的值,决定 dest 走 1or2 步

  2. 判断 dest 是否已经到结束

  3. cur++;

后面那种情况会报错,特殊处理边界情况就行:arr[n - 1] = 0; cur--; dest -= 2;

之后正常走,不影响

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

            if (dest >= n - 1)
                break;

            cur++;
        }

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

        // 复写
        while (cur >= 0)
        {
            if (arr[cur] != 0)
                arr[dest--] = arr[cur--];
            else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                --cur;
            }
        }
    }
};

快乐数

https://leetcode.cn/problems/happy-number/

判断一个数是不是快乐数

快乐数判定:

  1. 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和

  2. 然后重复这个过程直到这个数变为 1,也可能是 ++无限循环++ 但始终变不到 1

  3. 如果这个过程 结果为 1,那么这个数就是快乐数

两个情况都可以转化为链表相交问题:快慢双指针

判断相遇时的值即可,1 则是快乐数,非 1 则不是快乐数

这里是用数充当指针

cpp 复制代码
class Solution {
public:
    int Sum(int n) // 返回这个数每一位的平方和
    {
        int ret = 0;
        while (n)
        {
            int a = n % 10;
            ret += a * a;
            n /= 10;
        }
        return ret;
    }

    bool isHappy(int n) {
        int slow = n, fast = Sum(n);
        while (slow != fast)
        {
            slow = Sum(slow);
            fast = Sum(Sum(fast));
        }
        return slow == 1;
    }
};

盛水最多的容器

https://leetcode.cn/problems/container-with-most-water/

[1,8,6,2,5,4,8,3,7]

选 2 根线,返回最大水量

解法一:暴力枚举 O(N^2)

固定左边的线,依次取右边的线(2 层 for 循环)

解法二:利用单调性,使用双指针 O(N)

随便拿两个数研究区间,比如 [ 6,2,5,4 ]

先拿 6 和 4 计算,V = h * w

再++向内枚举++ ,让 4 分别和 2 5 计算,++宽度 w 永远减小;高度 h 要么减小,要么不变++ :V 永远减小

**找到规律:**left、right 算出 V 后,拿比较小的数向内枚举,V 永远减小,所以这个小的数不再考虑

**利用规律:**拿最左、最右的数算 V;干掉低的那一个再算 V,找 Vmax

cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0, right = height.size() - 1, Vmax = 0;
        while (left < right)
        {
            int V = (right - left) * min(height[left], height[right]);
            Vmax = max(Vmax, V);

            if (height[left] > height[right]) right--;
            else left++;
        }
        return Vmax;
    }
};

有效的三角形个数

https://leetcode.cn/problems/valid-triangle-number/

任取数组中 3 个数,判断能否构成三角形:任意两边之和大于第三边

解法一:暴力枚举 O(N^3)

cpp 复制代码
for (i = 0;)
    for (j = i + 1;)
        for (k = j + 1;)
            check(i, j, k);

优化:a<=b<=c 且 a+b>c 则能构成;否则不能构成。所以要给数组排序

解法二:利用单调性,双指针 O(N^2)
1. 先固定最大的数 N
2. 再在最大数的左区间内,用双指针快速统计个数N


a + b > c: 则这 3 数能构成 △;a 右边的数 > a,则 a 右边的数与 bc 都能构成 △,个数:ri - le

此时,right 左边的数都和 right、c 构成 △,并已统计个数,right 失去价值,right--


a + b <= c: 则这 3 数不能构成 △;b 左边的数 < b,则 b 左边的数与 ac 都不能构成 △

此时,left 右边的数都不能和 left、c 构成 △,left 失去价值,left++

cpp 复制代码
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        int ret = 0;
        sort(nums.begin(), nums.end());
        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])
                {
                    ret += right - left;
                    right--;
                }
                else
                {
                    left++;
                }
            }
        }

        return ret;
    }
};

和为 s 的两个数

https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/description/

输入一个递增数组和 s,查找两个数,使和正好是 s。如果有多对数字和是 s,则输出任意一对即可

解法一:暴力枚举,没有利用有序的特性

cpp 复制代码
for (i = 0;)
    for (j = i + 1;)

解法二:利用单调性,双指针

第一个数先和最后的数相加


sum > t: right 不动,left 后面的数是递增的,与 right 的和一定 > t,所以 right 没用了,right--


sum < t: left 不动,right 前面的数是递减的,与 left 的和一定 < t,所以 left 没用了,left++

**sum == t:**返回

cpp 复制代码
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 break;
        }
        vector<int> ret;
        ret.push_back(price[left]);
        ret.push_back(price[right]);
        return ret;
    }
};

简化:

cpp 复制代码
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 };
    }
};

三数之和

https://leetcode.cn/problems/3sum/

判断数组中是否存在 下标互不相同,且和为零的 3 个数;返回所有++互不相同(忽略顺序)++的三元组

第一个和第三个算相同的,要去重

为方便去重,防止出现上面的情况,要先排序

解法一:排序 + 暴力枚举 + 利用 set 去重 O(N^3)

解法二:排序 + 双指针 (这里我们不用 set 去重,太 low 了)
1. 排序
2. 固定一个 <=0 的数 a(正数后面一定是正数,和不可能为 0)
3. 在 a 后面的区间内,用双指针找和为 -a 的两个数

处理细节问题:
1. 不漏: 找到一组后不能停,要缩小区间继续找
2. 去重(注意细节,避免越界!!!)
找到一组后,left 和 right 要跳过重复元素
使用完一组双指针算法后,i 也要跳过重复元素

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> vv;
        int n = nums.size();
        for (int i = 0; i < n; )
        {
            if (nums[i] > 0) break;

            int left = i + 1, right = n - 1, t = -nums[i];
            while (left < right)
            {
                int sum = nums[left] + nums[right];
                if (sum < t) left++;
                else if (sum > t) right--;
                else
                {
                    vv.push_back({ nums[left], nums[right], nums[i] });
                    // 不漏
                    left++, right--;
                    // 去重 left right
                    while (left < right && nums[left - 1] == nums[left]) left++;
                    while (left < right && nums[right + 1] == nums[right]) right--;
                }
            }
            // 去重 i
            i++;
            while (i < n && nums[i - 1] == nums[i]) i++;
        }
        return vv;
    }
};

四数之和

https://leetcode.cn/problems/4sum/description/

给数组,找出下标互不相同,且和为 target 的 4 个数;返回所有++互不相同(忽略顺序)++的四元组

解法二:排序 + 双指针
1. 排序
2. 固定一个数 a
3. 在 a 后面的区间内,用"三数之和"找和为 target - a 的三个数
1. 固定一个数 b
2. 在 a 后面的区间内,用双指针找和为 target - a - b 的两个数

处理细节问题:
1. 不漏: 找到一组后不能停,要缩小区间继续找
2. 去重(注意细节,避免越界!!!)

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> vv;
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for (int j = 0; j < n; ) // 固定数 a
        {
            for (int i = j + 1; i < n; ) // 固定数 b
            {
                int left = i + 1, right = n - 1;
                long long t = (long long)target - nums[i] - nums[j];
                while (left < right)
                {
                    int sum = nums[left] + nums[right];
                    if (sum > t) right--;
                    else if (sum < t) left++;
                    else
                    {
                        vv.push_back({ nums[left], nums[right], nums[i], nums[j]});
                        // 不漏
                        left++, right--;
                        // 去重 left right
                        while (left < right && nums[left - 1] == nums[left]) left++;
                        while (left < right && nums[right + 1] == nums[left]) right--;
                    }
                }
                // 去重 b
                i++;
                while (i < n && nums[i - 1] == nums[i]) i++;
            }
            // 去重 a
            j++;
            while (j < n && nums[j - 1] == nums[j]) j++;
        }
        return vv;
    }
};

本篇的分享就到这里了,感谢观看 ,如果对你有帮助,别忘了点赞+收藏+关注

小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

相关推荐
项目申报小狂人2 小时前
基于迁移学习与丢弃法的神经网络算法在无人机失移动目标搜索中的应用,含代码
神经网络·算法·迁移学习
stolentime2 小时前
洛谷P15652 [省选联考 2026] 排列游戏 / perm题解
c++·算法·交互·洛谷·联合省选2026
仰泳的熊猫2 小时前
题目1834:蓝桥杯2016年第七届真题-路径之谜
数据结构·c++·算法·蓝桥杯·深度优先·图论
IT从业者张某某2 小时前
Docker部署Hadoop-02-Docker常见操作
hadoop·docker·容器
Darkwanderor2 小时前
数据结构——单调栈和单调队列
数据结构·c++·单调栈·单调队列
机器学习之心2 小时前
198种组合算法+优化SVR支持向量机回归+SHAP分析+新数据预测!机器学习可解释分析,强烈安利,粉丝必备!
算法·shap分析·新数据预测·优化svr支持向量机回归
宵时待雨2 小时前
C++笔记归纳8:stack & queue
开发语言·数据结构·c++·笔记·算法
Q鑫2 小时前
K8s之pod解析与调度策略
docker·容器·kubernetes
爱吃生蚝的于勒2 小时前
【Linux】网络基础(一)
linux·运维·服务器·网络·后端·算法·架构