【算法】优选 · 双指针

目录

移动零

复写零

快乐数

盛水最多的容器

有效的三角形个数

[和为 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;
    }
};

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

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

相关推荐
涛声依旧393162 分钟前
构建部署kubernetes所需主机
linux·运维·云原生·容器·kubernetes
源码之家3 分钟前
大数据毕业设计汽车推荐系统 Django框架 可视化 协同过滤算法 数据分析 大数据 机器学习(建议收藏)✅
大数据·python·算法·django·汽车·课程设计·美食
每天回答3个问题6 分钟前
LeetCodeHot100|对称二叉树、二叉树的直径、二叉树的层序遍历
数据结构·c++·ue4·
nianniannnn6 分钟前
力扣 3.无重复字符的最长子串
c++·算法·leetcode
羊小猪~~8 分钟前
【QT】-- 模型与视图简介
开发语言·数据库·c++·后端·qt·前端框架·个人开发
IT大师兄吖20 分钟前
flux-2-Klein-BFS-换头换脸工作流 懒人整合包
算法·宽度优先
水饺编程25 分钟前
第4章,[标签 Win32] :SysMets3 程序讲解02,iVertPos
c语言·c++·windows·visual studio
波哥学开发29 分钟前
深入解析 BEV 图像色彩调整与伪彩色映射:从直方图统计到着色器实现
算法·图形学
西西弟38 分钟前
最短路径之Floyd算法(数据结构)
数据结构·算法
小O的算法实验室44 分钟前
2026年SEVC,直觉模糊不确定环境下求解绿色多物品固定费用五维运输问题的多目标进化算法,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进