
⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///
WARNING \]: DETECTING HIGH ENERGY **🌊 🌉 🌊 心手合一 · 水到渠成**  |------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| | **\>\>\> ACCESS TERMINAL \<\<\<** || | [**\[ 🦾 作者主页 \]**](https://blog.csdn.net/fengtinghuqu520?spm=1000.2115.3001.5343) | [**\[ 🔥 C++初阶 \]**](https://blog.csdn.net/fengtinghuqu520/category_13085789.html) | | [**\[ 💾C++进阶 \]**](https://blog.csdn.net/fengtinghuqu520/category_13085793.html) | [**\[ 📡 代码仓库 \]**](https://blog.csdn.net/fengtinghuqu520/article/details/147275999?spm=1001.2014.3001.5502) | --------------------------------------- Running Process: 100% \| Latency: 0ms *** ** * ** *** #### 索引与导读 * [前言](#前言) * * [一、leetcode原题](#一、leetcode原题) * [二、题目分析](#二、题目分析) * * [2.1 核心目标](#2.1 核心目标) * [2.2 限制条件](#2.2 限制条件) * [2.3 数据规模](#2.3 数据规模) * [总结提取:](#总结提取:) * [三、算法设计思路](#三、算法设计思路) * * [3.1 排序 + 暴力枚举 + 利用 set 容器去重](#3.1 排序 + 暴力枚举 + 利用 set 容器去重) * * [代码实现](#代码实现) * [3.2 排序 + 双指针](#3.2 排序 + 双指针) * * [1)基本思路](#1)基本思路) * [2)处理细节问题](#2)处理细节问题) * [四、代码关键点解析(防止越界)](#四、代码关键点解析(防止越界)) * [五、分析时间与空间复杂度](#五、分析时间与空间复杂度) * * [5.1 时间复杂度分析](#5.1 时间复杂度分析) * [5.2 空间复杂度分析](#5.2 空间复杂度分析) * [💻结尾--- 核心连接协议](#💻结尾— 核心连接协议) ## 前言 本专栏深度聚焦**双指针算法** ,精准击破高频面试真题。不仅还原**算法原理** 与**解题思维** 的推演过程,更倾囊相授笔者的实战复盘笔记。**旨在通过典型案例拆解,助你构建由点及面的算法知识体系,高效攻克面试难关** ### 一、leetcode原题 > [🔗Lucy的空间骇客裂缝:](https://leetcode.cn/problems/3sum/description/)  *** ** * ** *** ### 二、题目分析 #### 2.1 核心目标 * 在给定的整数数组 nums 中,找出所有满足条件的 三元组 \[ n u m s \[ i \] , n u m s \[ j \] , n u m s \[ k \] \] \[nums\[i\], nums\[j\], nums\[k\]\] \[nums\[i\],nums\[j\],nums\[k\]
- 数学条件:这三个数的和必须等于 0,即 n u m s [ i ] + n u m s [ j ] + n u m s [ k ] = = 0 nums[i] + nums[j] + nums[k] == 0 nums[i]+nums[j]+nums[k]==0
2.2 限制条件
-
索引互异: 选出的三个数在原数组中的下标必须互不相同( i ≠ j i \neq j i=j, i ≠ k i \neq k i=k 且 j ≠ k j \neq k j=k)
-
去重要求: 答案中 不可以包含重复 的三元组。这意味着如果结果中已经有了
[-1, 0, 1],就不能再放入[0, -1, 1]
2.3 数据规模
-
数组长度: 3 ≤ n u m s . l e n g t h ≤ 3000 3 \le nums.length \le 3000 3≤nums.length≤3000。
- 解读:由于长度达到了 3000,使用 O ( N 3 ) O(N^3) O(N3) 的纯暴力三层循环
-
数值范围: − 10 5 ≤ n u m s [ i ] ≤ 10 5 -10^5 \le nums[i] \le 10^5 −105≤nums[i]≤105
- 解读:三个数相加可能达到 3 × 10 5 3 \times 10^5 3×105 或 − 3 × 10 5 -3 \times 10^5 −3×105,在标准 int 范围内,不需要担心溢出问题
总结提取:
这道题的难点不在于"找和为 0",而在于如何在不超时的情况下 彻底去重 。排序 是处理去重最有效的预处理手
三、算法设计思路
3.1 排序 + 暴力枚举 + 利用 set 容器去重
- 排序
先执行sort(nums.begin(), nums.end())有两个巨大好处:- 方便去重: 排序后,相同的三元组(如
[1, -1, 0])都会变成统一的顺序(如[-1, 0, 1])。这样存入std::set时,重复项会被自然过滤 - 剪枝优化: 如果排序后第一个数就大于 0,那么后面的数都大于 0,和绝对不可能为 0,可以直接停止
- 方便去重: 排序后,相同的三元组(如
- 三层循环
通过三层 for 循环遍历数组,尝试所有索引 i , j , k i, j, k i,j,k 的组合:
- 利用
set去重- 为了满足题目"不包含重复三元组"的要求,我们将找到的每一个满足条件的三元组存入一个
std::set<vector<int>> - 由于
set会自动去重,最后将set中的内容转回vector返回即可
- 为了满足题目"不包含重复三元组"的要求,我们将找到的每一个满足条件的三元组存入一个
代码实现
cpp
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
// 1. 排序:为了让重复的三元组具有相同的排列顺序
sort(nums.begin(), nums.end());
set<vector<int>> resultSet; // 存储结果并自动去重
// 2. 暴力三层嵌套循环
for (int i = 0; i < n; ++i) {
// 优化:如果当前数字大于0,后面三个数之和必然大于0
if (nums[i] > 0) break;
for (int j = i + 1; j < n; ++j) {
for (int k = j + 1; k < n; ++k) {
if (nums[i] + nums[j] + nums[k] == 0) {
// 3. 存入 set,set 会根据内容自动判断是否重复
resultSet.insert({nums[i], nums[j], nums[k]});
}
}
}
}
// 将 set 转换为题目要求的 vector 格式
return vector<vector<int>>(resultSet.begin(), resultSet.end());
}
};
3.2 排序 + 双指针
1)基本思路
-
排序;
-
定义一个指针
i固定一个数a;
当a<=0的时候才考虑后面双指针区域 -
在该数后面的区间内,利用 "双指针算法"
快速找到两个的和等于
-a即可

cpp
class Solution {
public:
vector<vector<int>>
threeSum(vector<int>& nums) { // 存储符合三叔之和的数组函数
//这里我们定义一个数组来存储,不能用函数去存储
vector<vector<int>> ret;
// 1.排序
sort(nums.begin(), nums.end());
int n = nums.size();
// 2.固定一个数a
for (int i = 0; i < n; i++) {
if (a > 0)
break;
int left = i + 1, right = n - 1,target = -nums[i];
// 3.双指针操作
while (left < right) {
int sum = nums[left] + nums[right];
if (sum < target) {
left++;
} else if (sum > target) {
right--;
} else {
ret.push_back({nums[i], nums[left], nums[right]});
}
}
}
}
};
2)处理细节问题
在上一种容器去重 的解法中,如果我们在笔试中运用这种的解法是可以通过的;但是如果在面试中面试官问你有没有另外一种解法,这时候你就会被难住了
所以这个细节问题,我们主要处理去重和不漏,以及防止越界
-
去重
- 找到一种结果之后,
left和right指针要跳过重复元素 - 当使用完一次双指针算法之后,
i也需要跳过重复元素
- 找到一种结果之后,
-
不漏
找到一种结果之后,不要"停",缩小区间,继续寻找
-
防止越界
这个我们通过后面的代码讲解
cpp
class Solution {
public:
vector<vector<int>>
threeSum(vector<int>& nums) { // 存储符合三叔之和的数组函数
// 这里我们定义一个数组来存储,不能用函数去存储
vector<vector<int>> ret;
// 1.排序
sort(nums.begin(), nums.end());
int n = nums.size();
// 2.固定一个数a
for (int i = 0; i < n; ) {
if (nums[i] > 0)
break;
int left = i + 1, right = n - 1, target = -nums[i];
// 3.双指针操作
while (left < right) {
int sum = nums[left] + nums[right];
if (sum < target) {
left++;
} else if (sum > target) {
right--;
} else {
ret.push_back({nums[i], nums[left], nums[right]});
// 不漏
left++, right--;
// 去重
while (left < right && nums[left] == nums[left - 1]) {
left++;
}
while (left < right && nums[right] == nums[right + 1]) {
right--;
}
}
}
i++;
while (i < n && nums[i] == nums[i - 1]) {
i++;
}
}
return ret;
}
};
四、代码关键点解析(防止越界)
cpp
for (int i = 0; i < n; )
假设数组为 [-2, -2, -2, 0, 2]:
-
第一轮循环:
i = 0,固定数字-2。双指针在后面找到了[-2, 0, 2]。 -
循环末尾:
- 执行
i++,此时i = 1。 - 进入
while循环,发现nums[1]等于nums[0](都是-2),执行i++,此时i = 2。 - 继续
while循环,发现nums[2]等于nums[1](都是-2),执行i++,此时i = 3。 - 此时
nums[3] = 0,不等于nums[2],while停止。
- 执行
-
第二轮循环:
i直接从3开始,固定数字0。
结果: 我们成功跳过了中间那两个多余的 -2,避免了重复计算。
如果我们在for循环加一个i++,就会跳过一些元素,会导致漏掉情况和越界
五、分析时间与空间复杂度
5.1 时间复杂度分析
该算法的时间消耗主要分为两个部分:排序 和 双指针遍历
A. 排序阶段
- 使用的是
std::sort,在C++中通常是快速排序、堆排序和插入排序的混合 - 复杂度: O ( N log N ) O(N \log N) O(NlogN),其中 N N N 是数组 nums 的长度。
B. 双指针遍历阶段
-
外层循环: 遍历变量 ( i ),最多执行 ( N ) 次。
-
内层双指针 : 对于每一个固定的 ( i ),
left和right分别从两端向中间靠拢。由于left只增不减,right只减不增,内层while循环的总移动次数也是 ( O(N) )。 -
去重操作 : 虽然代码中有多个
while用于跳过重复元素,但这些操作本质上只是让指针走得更快,并没有增加总的遍历次数。每个元素在每一轮外层循环中最多被访问一次。 -
总计: 外层 ( N ) 次 × 内层 ( N ) 次 = ( O(N^2) )。
结论
T(n) = O(N \\log N) + O(N\^2) = O(N\^2)
在 ( N ) 较大时,( O(N^2) ) 占据主导地位。
5.2 空间复杂度分析
空间复杂度需要根据是否考虑结果数组 以及 排序产生的栈空间来划分
A. 辅助空间
-
指针与变量 :
i,left,right,target,sum等变量仅占用常数空间 ( O(1) )。 -
排序开销 :
std::sort在C++中的实现通常需要 ( O(log N) ) 的递归栈空间。 -
结论 :
( O(log N) )
B. 结果空间
-
返回数组 :
ret存储所有符合条件的三元组。在最坏情况下(例如数组全为0),三元组的数量可能达到 ( O(N^2) ) 级别。 -
注意 :
在算法竞赛和面试中,通常不计入返回结果所需的空间,除非题目特别要求。
结论
-
如果不计结果数组: ( O(\log N) ) (取决于排序算法)。
-
如果计入结果数组: 最坏情况下可达 ( O(N^2) )。
💻结尾--- 核心连接协议
警告: 🌠🌠正在接入底层技术矩阵。如果你已成功破解学习中的逻辑断层,请执行以下指令序列以同步数据:🌠🌠
【📡】 建立深度链接: 关注本终端。在赛博丛林中深耕底层架构,从原始代码到进阶协议,同步见证每一次系统升级。
【⚡】 能量过载分发: 执行点赞操作。通过高带宽分发,让优质模组在信息流中高亮显示,赋予知识跨维度的传播力。
【💾】 离线缓存核心: 将本页加入收藏。把这些高频实战逻辑存入你的离线存储器,在遭遇系统崩溃或需要离线检索时,实现瞬时读取。
【💬】 协议加密解密: 在评论区留下你的散列码。分享你曾遭遇的代码冲突或系统漏洞(那些年踩过的坑),通过交互式编译共同绕过技术陷阱。
【🛰️】 信号频率投票: 通过投票发射你的选择。你的每一次点击都在重新定义矩阵的进化方向,决定下一个被全量拆解的技术节点。

