算法修炼 chapter 2 双指针进阶、盛最多水的容器、有效三角形的个数、两数之和、三数之和、四数之和

算法修炼 chapter 2 双指针进阶

摘要

本章学习双指针技法在容器盛水、三角形判断、两数/三数/四数之和等问题中的应用,涵盖5道经典题。

题目 来源 核心考点
11. 盛最多水的容器 LeetCode 对撞指针求最大面积
611. 有效三角形的个数 LeetCode 排序 + 对撞指针计数
LCR 179. 查找总价格为目标值的两个商品 LeetCode 有序数组两数之和
15. 三数之和 LeetCode 排序 + 对撞指针去重
18. 四数之和 LeetCode 三数之和扩展,注意 long long 防溢出

关键技法

  • 对撞指针:两端向中间逼近,每次收缩较小(或较大)的一侧
  • 排序预处理:有序是双指针的前提,也是去重的基础
  • 去重:找到一组解后,跳过所有相邻重复值,避免结果重复

易错点

  • if/else 多行语句必须加花括号,否则只有第一行受条件控制
  • 变量名不要用 max,与 std::max 冲突,可能编译报错
  • 四数之和中三数之和的 aim 要用 long long,防止 nums[i]+nums[j]+target 溢出
  • 去重时注意比较的是同侧的前驱元素:nums[right] == nums[right+1],不是 nums[left+1]
  • 三数之和/四数之和固定第一个数后,若该数 > 0 可直接提前终止(全正数不可能凑出 0)

1. LeetCode 11 --- 盛最多水的容器

题目链接: 力扣链接

题解

cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& h) {
        int left = 0;
        int right = h.size() - 1;
        int retMax = 0;
        while (left < right) {
            if (h[left] < h[right]) {
                int ret = (right - left) * h[left++];
                if (ret > retMax) retMax = ret;
            } else {
                int ret = (right - left) * h[right--];
                if (ret > retMax) retMax = ret;
            }
        }
        return retMax;
    }
};

解题思路

暴力枚举

枚举出所有能构成的容器,找出其中的最大值。设两指针 i、j 分别指向水槽板的最左端和最右端,此时容器的宽度为 j - i。由于容器的高度由两板中的短板决定,因此可得容积公式:V = (j - i) * min(height[i], height[j])。暴力法时间复杂度 O(n²),会超时。
对撞指针

设两个指针 left、right 分别指向容器的左右两个端点,此时容器的容积:V = (right - left) * min(height[right], height[left])。容器的左边界为 height[left],右边界为 height[right]。用 ret 记录这个容积,与 retMax(初始值为 0)作比较,如果 ret > retMax,则 retMax = ret

然后移动较短的那一边向中间靠拢:如果左边较短,left++;如果右边较短,right--。因为宽度一定在减小,只有移动到更高的板才有可能获得更大的面积。继续这个过程直到两指针相遇,返回 retMax


2. LeetCode 611 --- 有效三角形的个数

题目链接: 力扣链接

题解

cpp 复制代码
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int ret = 0, n = nums.size();
        for (int i = n - 1; i >= 2; i--) {
            int left = 0, right = i - 1;
            while (left < right) {
                if (nums[left] + nums[right] > nums[i]) {
                    // [left, right-1] 中的所有元素与 nums[right] 都满足三角不等式
                    ret += right - left;
                    right--;
                } else {
                    left++;
                }
            }
        }
        return ret;
    }
};

解题思路

  1. 根据「解法一」中的优化思想,我们可以固定一个「最长边」,然后在比这条边小的有序数组中找出一个二元组,使这个二元组之和大于这条最长边。由于数组已排序,可以利用「对撞指针」来优化。
  2. 设最长边枚举到 i 位置,区间 [left, right] 是 i 位置左边的区间(也就是比它小的区间):
    • 如果 nums[left] + nums[right] > nums[i]:说明 [left, right-1] 区间上的所有元素均可以与 nums[right] 构成满足条件的二元组,满足条件的有 right - left 种。此时 right 位置的所有情况已考虑完毕,right--,进入下一轮判断。
    • 如果 nums[left] + nums[right] <= nums[i]:说明 left 位置的元素不可能与 [left+1, right] 位置上的元素构成满足条件的二元组(因为 nums[left] 已经是当前最小的了),left++ 进入下一轮循环。

3. LeetCode LCR 179 --- 查找总价格为目标值的两个商品

题目链接: 力扣链接

题解

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& p, int target) {
        int left = 0;
        int right = p.size() - 1;
        while (left < right) {
            int sum = p[left] + p[right];
            if (sum > target)
                right--;
            else if (sum < target)
                left++;
            else
                break;
        }
        return {p[left], p[right]};
    }
};

解题思路

  1. 题目已保证数组有序(单调递增)。设左右两个指针 left、right,用 p[left] + p[right] 和 target 比较:
    • 如果和大于 target,说明右边的数太大,right-- 缩小右边界;
    • 如果和小于 target,说明左边的数太小,left++ 扩大左边界;
    • 如果相等,直接跳出循环。
  2. 返回 {p[left], p[right]} 即可。

注意 :本题数组已有序,无需额外排序。如果输入无序,需先 sort 再使用对撞指针。


4. LeetCode 15 --- 三数之和

题目链接: 力扣链接

题解

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ret;
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for (int i = 0; i < n;) {
            if (nums[i] > 0) break;  // 最小的数已经大于0,不可能有三数之和为0
            int left = i + 1, right = n - 1, target = -nums[i];
            while (left < right) {
                int sum = nums[left] + nums[right];
                if (sum > target)
                    right--;
                else if (sum < target)
                    left++;
                else {
                    ret.push_back({nums[i], nums[left++], nums[right--]});
                    // 跳过重复的 left
                    while (left < right && nums[left] == nums[left - 1]) left++;
                    // 跳过重复的 right
                    while (left < right && nums[right] == nums[right + 1]) right--;
                }
            }
            i++;
            while (i < n && nums[i] == nums[i - 1]) i++;  // 跳过重复的 i
        }
        return ret;
    }
};

解题思路

  1. 为了提高效率,先给数组从小到大排序。与两数之和不同的是,题目要求找到所有「不重复」的三元组。
  2. 固定一个数 a = nums[i],问题转化为在 i+1 ~ n-1 区间内找两数之和等于 -a,利用对撞指针实现。
  3. 去重关键
    • 固定 i 时,如果 nums[i] == nums[i-1] 则跳过,避免重复三元组。
    • 找到一组解后,left 和 right 都需要跳过所有相邻重复值。
  4. 如果 nums[i] > 0,由于数组已排序,后续所有数都为正,三数之和不可能为 0,直接 break

5. LeetCode 18 --- 四数之和

题目链接: 力扣链接

题解

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ret;
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for (int i = 0; i < n;) {
            for (int j = i + 1; j < n;) {
                int left = j + 1, right = n - 1;
                long long aim = (long long)target - nums[i] - nums[j];
                while (left < right) {
                    long long sum = (long long)nums[left] + nums[right];
                    if (sum < aim)
                        left++;
                    else if (sum > aim)
                        right--;
                    else {
                        ret.push_back({nums[i], nums[j], nums[left++], nums[right--]});
                        // 跳过重复的 left
                        while (left < right && nums[left] == nums[left - 1]) left++;
                        // 跳过重复的 right
                        while (left < right && nums[right] == nums[right + 1]) right--;
                    }
                }
                j++;
                while (j < n && nums[j] == nums[j - 1]) j++;
            }
            i++;
            while (i < n && nums[i] == nums[i - 1]) i++;
        }
        return ret;
    }
};

解题思路

  1. 与三数之和的思想类似,固定左边的两个数 nums[i]nums[j],剩下的用对撞指针找两数之和等于 target - nums[i] - nums[j]
  2. 注意 long long 防溢出aimsum 都使用 long long,防止 nums[i] + nums[j] + target 或两数相加时整型溢出。
  3. 去重逻辑与三数之和完全一致,三层循环每一层都要跳过相邻重复值。
  4. 四数之和的时间复杂度为 O(n³),空间复杂度 O(1)(不计返回值)。

任何的批评和建议欢迎指出,我们共同进步!

相关推荐
你压到我腿毛了66610 分钟前
C语言冒泡算法(Bubble sort)
c语言·数据结构·算法
靠沿1 小时前
贪心算法专题(三)
算法·贪心算法
super大力张1 小时前
MOE基于结构的药物设计(七):Ligand R-Vectors——如何判断配体可以从哪里继续生长?
数据库·算法·cadd·moe·svl·基于结构的药物设计
Tongsr2 小时前
别只混淆代码:用 Kaleido 加固整个 Android Release AAB
前端·算法·github
高亦真2 小时前
今天是学习嵌入式的第34天
linux·学习·算法
飞Link3 小时前
动作方法中的分割与过度分割方法全解析(含代码实战与踩坑案例)
人工智能·python·算法·计算机视觉
张宇Joaquin4 小时前
鲲鹏统一并行加速库KUPL--异步数据拷贝
线性代数·算法·矩阵·numpy
GlobalInfo4 小时前
创新不是追逐热点,是在变化中建立长期竞争力
大数据·算法
lvwangshu4 小时前
线性代数基础 Linear Algebra Basics
数学·算法