当前位置: 首页 > news >正文

优选算法 力扣 18. 四数之和 双指针算法的进化 优化时间复杂度 C++ 题解 每日一题

文章目录

  • 一、题目描述
  • 二、为什么这道题值得你花几分钟看懂?
  • 三、题目拆解:提取其中的关键点
  • 四、明确思路:从暴力到优化
  • 五、算法实现:固定双元素 + 双指针
  • 六、C++代码实现:一步步拆解
    • 代码拆解
    • 时间复杂度
    • 空间复杂度
  • 七、实现过程中的坑点总结
  • 八、举一反三
  • 九、下题预告

一、题目描述

题目链接:四数之和

题目描述:
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回所有满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]](若两个四元组元素一一对应,则认为它们重复):
0 <= a, b, c, d < n
a、b、c、d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
注意:答案中不可以包含重复的四元组。

示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

提示:
1 <= nums.length <= 200
10^9 <= nums[i] <= 10^9
10^9 <= target <= 10^9

二、为什么这道题值得你花几分钟看懂?

四数之和作为 LeetCode 第 18 题,是多指针算法的进阶经典题,几乎是大厂算法面试的“常驻嘉宾”。它在三数之和的基础上增加了一层复杂度,能帮你彻底掌握“固定元素 + 双指针”的核心套路,而这种思路是解决 N 数之和、组合求和等一系列问题的关键。

从能力提升来看,这道题能让你深刻理解:

  • 如何在多层循环中高效去重,避免重复解(这是很多人卡壳的核心难点);
  • 如何通过问题转化降低复杂度(将四数之和转化为两数之和);
  • 如何处理数值溢出等边界问题(int 范围下的大数计算陷阱)。

学会这道题,你能举一反三解决:

  • 三数之和(LeetCode 15)
  • 四数相加 II(LeetCode 454)
  • 组合总和(LeetCode 39)等类似问题

生活中也有很多场景能用到这种思路,比如电商平台的“满减组合推荐”(从商品列表中找出多个商品总价满足目标优惠)、财务系统的“多笔交易总和核对”等,核心都是“在一堆数字中找到满足目标和的组合”。

三、题目拆解:提取其中的关键点

结合代码框架和题目要求,核心要素如下:

  1. 输入是一个整数数组 nums 和目标值 target,数组长度在 1~200 之间,数值范围达 ±10^9(需注意溢出风险)。
  2. 需返回所有不重复的四元组 [a,b,c,d],满足四个数之和等于 target,且索引互不相同。
  3. 四元组的“不重复”指元素值一一对应相同(如 [2,2,2,2] 即使索引不同也只算一个)。

关键点提炼

  • 多层循环:通过两层循环固定前两个数,再用双指针找后两个数。
  • 去重逻辑:对固定的两个数和双指针找到的数分别去重,避免重复四元组。
  • 溢出处理:计算目标和时使用 long long 防止整数溢出。
  • 边界控制:确保指针索引合法(不越界、不重复)。

四、明确思路:从暴力到优化

1. 暴力解法的困境
最直观的思路是四层循环遍历所有可能的四元组,检查和是否等于 target,但时间复杂度为 O(n⁴),在 n=200 时会达到 200⁴=1.6×10⁹ 次操作,显然超时。

2. 优化核心:固定 + 双指针
借鉴三数之和的思路,通过固定两个元素,将问题转化为两数之和,把时间复杂度降至 O(n³):

  • 先对数组排序(为去重和双指针移动铺路)。
  • 外层循环固定第一个元素 nums[i],内层循环固定第二个元素 nums[j]j > i)。
  • 剩余两个元素用双指针在 [j+1, n-1] 范围内寻找,目标和为 target - nums[i] - nums[j]

3. 去重的必要性
排序后数组中可能有重复元素,若不处理会导致重复四元组。例如 nums = [2,2,2,2,2],固定不同位置的 2 会生成相同的四元组,因此需要在固定元素和移动指针时跳过重复值。

4. 溢出防护
由于数值范围达 ±10⁹,四数之和可能超过 int 类型的最大值(2¹⁴⁷⁴⁸³⁶⁴⁷),因此计算中间目标和时需用 long long 类型。

五、算法实现:固定双元素 + 双指针

核心逻辑

  1. 排序数组,为去重和双指针移动提供基础。
  2. 两层循环固定前两个元素 nums[i]nums[j]i < j)。
  3. 计算目标和 aim = target - nums[i] - nums[j],用双指针 leftj+1)和 rightn-1)寻找和为 aim 的两个数。
  4. 找到符合条件的四元组后,移动指针并去重;若未找到,根据当前和与 aim 的大小关系移动指针。
  5. 对固定元素 ij 进行去重,避免重复计算。

六、C++代码实现:一步步拆解

完整代码(附详细注释):

#include <vector>
#include <algorithm>
using namespace std;class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> ret;  // 存储结果// 1.排序:为去重和双指针移动做准备sort(nums.begin(), nums.end());int n = nums.size();  // 数组长度// 2.外层循环固定第一个元素a(nums[i])for (int i = 0; i < n;) {// 3.内层循环固定第二个元素b(nums[j]),j > ifor (int j = i + 1; j < n;) {// 4.双指针寻找c和d,范围是[j+1, n-1]int left = j + 1, right = n - 1;// 计算目标和:用long long避免溢出long long aim = (long long)target - nums[i] - nums[j];// 双指针循环while (left < right) {int sum = 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--]});// 去重一:跳过左指针重复元素while (left < right && nums[left] == nums[left - 1]) {left++;}// 去重二:跳过右指针重复元素while (left < right && nums[right] == nums[right + 1]) {right--;}}}// 去重三:跳过第二个固定元素j的重复值j++;while (j < n && nums[j] == nums[j - 1]) {j++;}}// 去重四:跳过第一个固定元素i的重复值i++;while (i < n && nums[i] == nums[i - 1]) {i++;}}return ret;}
};

代码拆解

1. 排序预处理

sort(nums.begin(), nums.end());

作用

  • 使重复元素相邻,便于后续去重;
  • 保证双指针移动时能根据和的大小调整方向(左指针右移增大和,右指针左移减小和)。

2. 固定元素 ij 的循环

for (int i = 0; i < n;) {  // 固定第一个元素for (int j = i + 1; j < n;) {  // 固定第二个元素,j > i确保索引不同// ... 双指针逻辑 ...}
}

核心设计

  • ij 的循环条件不带自增(i++j++ 放在循环体内),为去重留空间;
  • j = i + 1 确保 ji 右侧,避免索引重复。

3. 双指针逻辑

int left = j + 1, right = n - 1;  // 左指针在j右侧,右指针在末尾
long long aim = (long long)target - nums[i] - nums[j];  // 目标和while (left < right) {  // 左指针 < 右指针才有效int sum = 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--]});// ... 去重逻辑 ...}
}

关键细节

  • aimlong long 计算,防止 target - nums[i] - nums[j] 溢出(例如当 target=1e9nums[i]nums[j] 均为 1e9 时,结果为负数,用 int 会溢出);
  • 找到四元组后,left++right-- 同时移动,继续寻找下一组可能的组合。

4. 四层去重逻辑

// 1. 左指针去重:跳过与前一个元素相同的值
while (left < right && nums[left] == nums[left - 1]) left++;// 2. 右指针去重:跳过与后一个元素相同的值
while (left < right && nums[right] == nums[right + 1]) right--;// 3. 第二个固定元素j去重:跳过与前一个j相同的值
j++;
while (j < n && nums[j] == nums[j - 1]) j++;// 4. 第一个固定元素i去重:跳过与前一个i相同的值
i++;
while (i < n && nums[i] == nums[i - 1]) i++;

去重原理

  • 排序后重复元素相邻,因此只需比较当前元素与前一个元素(ijleft)或后一个元素(right);
  • 去重操作放在指针移动后,确保先处理当前元素,再跳过重复值。

示例运行过程(以示例 1 为例)
输入:nums = [1,0,-1,0,-2,2]target = 0
排序后:[-2, -1, 0, 0, 1, 2]

  1. i=0nums[i]=-2),j=1nums[j]=-1):

    • aim = 0 - (-2) - (-1) = 3
    • 双指针 left=2(0),right=5(2),sum=0+2=2 < 3left++
    • left=3(0),right=5(2),sum=0+2=2 < 3left++
    • left=4(1),right=5(2),sum=1+2=3 == aim → 记录 [-2,-1,1,2]
    • 去重后 left=5right=4,双指针循环结束。
  2. 后续 ij 继续循环,依次找到 [-2,0,0,2][-1,0,0,1],最终返回这三个四元组。

时间复杂度

操作类型时间复杂度说明
排序预处理O(n log n)快速排序的时间复杂度
外层两层循环O(n²)固定两个元素的循环
内层双指针循环O(n)每个内层循环中双指针遍历
总计O(n³)由 O(n² × n) 主导

空间复杂度

  • O(log n) 至 O(n):取决于排序算法的空间复杂度(快速排序为 O(log n),归并排序为 O(n)),结果存储不计入额外空间。

七、实现过程中的坑点总结

  1. 整数溢出问题

    • 错误写法int aim = target - nums[i] - nums[j];
    • 问题:当 targetnums[i]nums[j] 取值较大时,计算结果可能超出 int 范围(如 1e9 - (-1e9) - (-1e9) = 3e9 > 2e9)。
    • 解决:用 long long 存储中间结果:long long aim = (long long)target - nums[i] - nums[j];
  2. 去重时机错误

    • 错误写法:在固定元素前先去重(如 i 初始化为 1,跳过 nums[i] == nums[i-1])。
    • 问题:会漏掉第一个元素的处理(如 i=0 时的元素)。
    • 解决:先处理当前元素,再在循环体内去重(i++ 后检查 nums[i] == nums[i-1])。
  3. 指针越界

    • 错误写法:去重时未判断 left < rightj < n
    • 问题:可能导致指针超出数组范围(如 left 超过 right 后仍访问 nums[left])。
    • 解决:去重逻辑需带边界条件(如 while (left < right && ...))。
  4. 索引重复

    • 错误写法j 的初始值为 i 而非 i+1
    • 问题:导致 ij 索引相同,不符合“四元组索引互不相同”的要求。
    • 解决:确保 j = i + 1left = j + 1,保证四个索引严格递增。

八、举一反三

掌握四数之和的思路后,可轻松解决以下问题:

  • 三数之和(LeetCode 15):固定一个元素,用双指针找另外两个,目标和为 0。
  • 五数之和:固定三个元素,转化为两数之和,时间复杂度 O(n⁴)。
  • 组合总和 II(LeetCode 40):类似多数之和,需去重且元素只能使用一次。

核心规律:k 数之和问题可通过固定 k-2 个元素,转化为两数之和,时间复杂度为 O(n^(k-1)),且去重逻辑需针对每个固定元素和双指针分别处理。

九、下题预告

明天将讲解 LeetCode 209. 长度最小的子数组,这道题是滑动窗口算法的经典入门题,核心是如何用双指针在 O(n) 时间内找到和大于等于目标值的最短子数组。它和今天的四数之和同为双指针技巧,但思路截然不同(一个是固定元素找组合,一个是动态调整窗口范围),敬请期待!

如果觉得这篇解析有帮助,不妨:
🌟 点个赞,让更多人看到这份清晰思路
⭐ 收个藏,下次复习时能快速找回灵感
👀 加个关注,明天的滑动窗口解析更精彩~

这是封面原图:
在这里插入图片描述

http://www.dtcms.com/a/327999.html

相关文章:

  • Perl——$_
  • Bevy渲染引擎核心技术深度解析:架构、体积雾与Meshlet渲染
  • UE5.3 C++ 动态多播实战总结
  • AtCoder Beginner Contest 418 C-E 题解
  • 数据分析小白训练营:基于python编程语言的Numpy库介绍(第三方库)(上篇)
  • python3 undefined symbol: _Py_LegacyLocaleDetected
  • C语言结构体与内存分配:构建复杂数据结构的基石
  • 【AI驱动的语义通信:突破比特传输的下一代通信范式】
  • Git 常用命令速查表
  • 最终章【1】Epson机器人篇
  • 微服务项目中的注册中心——Nacos配置
  • 【每日一题】Day 1
  • ETCD备份
  • Flask + Vue.js 物联网数字大屏实现方案
  • 学习:JS[9]作用域+函数进阶+深入对象+实例成员和静态成员
  • 为什么神经网络的权重矩阵具有低秩特性?如何理解和解释?
  • [Robotics_py] 机器人运动模型 | `update`函数 | 微积分矩阵
  • 线性代数 · 矩阵 | 最小多项式
  • 有限元方法中的数值技术:行列式、求逆、矩阵方程
  • 企业高性能web服务器(1)
  • 腾讯云服务器账户转移操作详解
  • ip归属地批量查询脚本
  • vue2+elementUI实现园型动态步骤条小组件,带缩放功能
  • ENCOPIM, S.L. 参展 AUTO TECH China 2025 广州国际汽车技术展览会
  • 基于STC8单片机的RTC时钟实现:从原理到实践
  • Cloud Computing(云计算)和Sky Computing(天空计算)
  • 自然语言处理关键库解析和使用方法- FuzzyWuzzy
  • kafka初步介绍
  • mysql登录失败 ERROR1698
  • Java零基础笔记15(Java编程核心:Stream流、方法中的可变参数、Collections工具类)