LeetCode 双指针题型 C++ 解题整理

双指针算法基础

  1. 两种核心形式

(1)对撞指针(左右指针)

适用场景:顺序结构(数组、字符串等),需要从两端向中间逼近的问题。

移动规则:一个指针从最左端开始,另一个从最右端开始,逐步向中间移动。

终止条件:

left == right:两个指针指向同一位置

left > right:两个指针错开(循环结束)

典型问题:两数之和 II、验证回文串、盛最多水的容器等。

(2)快慢指针(龟兔赛跑算法)

适用场景:环形链表、数组循环问题、需要区分移动速度的场景。

核心思想:使用两个移动速度不同的指针(慢指针一次走1步,快指针一次走2步)在序列上移动。

典型问题:环形链表检测、寻找链表中点、删除链表倒数第N个节点等。


题目1:移动零(LeetCode 283)

  1. 题目描述

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序,且必须原地修改数组,不复制数组。

示例:输入:nums = [0,1,0,3,12] 输出:[1,3,12,0,0]

  1. 解题思路(快排分区思想)

核心思想:将数组划分为两个区间

0, dest\]:全部为非零元素 \[dest+1, cur-1\]:全部为零元素 指针定义 cur:遍历指针,从左到右扫描整个数组 dest:记录非零元素序列的最后一个位置,初始为 -1(表示初始无任何非零元素) 3. 算法流程 1. 初始化:cur = 0(遍历起点),dest = -1(非零区间末尾) 2. 遍历数组:cur 从 0 到 nums.size()-1: 若 nums\[cur\] != 0: dest++(非零区间右扩一位); 交换 nums\[dest\] 和 nums\[cur\](将当前非零元素放到非零区间末尾); cur++(继续扫描下一个元素) 若 nums\[cur\] == 0: 直接 cur++(零元素暂时留在原位置,后续会被非零元素覆盖) 3. 遍历结束:\[0, dest\] 为所有非零元素,\[dest+1, nums.size()-1\] 为所有零元素。 4. C++ 代码实现 ```cpp class Solution { public: void moveZeroes(vector& nums) { for (int cur = 0, dest = -1; cur < nums.size(); cur++) { if (nums[cur]) // 处理非0元素 swap(nums[cur], nums[++dest]); } } }; ``` 5. 复杂度分析 时间复杂度:O(n),仅遍历数组一次,交换操作也是 O(1) 空间复杂度:O(1),原地修改,无额外空间开销 稳定性:保持了非零元素的相对顺序,符合题目要求 *** ** * ** *** ## 题目2:复写零(LeetCode 1089) 1. 题目描述 给定一个长度固定的整数数组 arr,将数组中每个 0 都复写一遍,其余元素向右平移,且不能超出数组长度,必须原地修改数组。 示例:输入:arr = \[1,0,2,3,0,4,5,0\] 输出:\[1,0,0,2,3,0,0,4

  1. 解题思路(从后往前双指针)

核心问题:若从前往后复写,0 会被复写两次,导致未处理的元素被覆盖,故选择从后往前复写策略。

核心流程:

  1. 找到最后一个会被复写的元素:模拟复写过程,确定复写后数组的边界。

  2. 从后往前复写:从边界开始,将原数组元素复写到目标位置,避免覆盖未处理元素。

  3. 算法流程

步骤1:找到最后一个"复写"的数

指针定义:

cur:遍历原数组的指针,从左到右移动。dest:模拟复写后的位置指针,记录复写后的末尾位置。

流程:

  1. 判断cur位置的值:

如果arr[cur] == 0,dest += 2(因为0要复写两次)。

如果arr[cur] != 0,dest += 1(非零元素只移动一次)。

  1. 判断dest是否到达数组末尾(dest >= n-1),如果是则停止。(n为数组size大小)

  2. cur++,继续遍历下一个元素。

步骤1.5:处理边界情况(例如[1,0,2,3,0,4])

如果dest == n(说明最后一个元素是0且复写后越界):

将数组最后一个位置设为0:arr[n-1] = 0。

cur--(回退一步,跳过越界的0)。 dest -= 2(回退两位,回到有效边界)。

步骤2:从后向前完成复写操作

从cur和dest的位置开始,从后往前遍历:

如果arr[cur] == 0:arr[dest--] = 0 arr[dest--] = 0(复写两次) cur--

如果arr[cur] != 0:arr[dest--] = arr[cur--](直接移动非零元素)

直到cur < 0时结束。

  1. C++ 代码实现
cpp 复制代码
class Solution 
{
public:
    void duplicateZeros(vector<int>& arr) 
    {
        // 找到最后一个元素
        int cur = 0 , dest = -1;
        while(cur < arr.size())
        {
            if(arr[cur]) dest++;
            else dest += 2;
            if(dest >= arr.size()-1) break;
            cur ++;
        }

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

        // 从后往前完成复写操作
        while(cur >= 0)
        {
            if(arr[cur]) arr[dest--] = arr[cur--];
            else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur --;
            }
        }
    }
};
  1. 复杂度分析

时间复杂度:O(n),遍历数组两次(一次定位边界,一次复写)

空间复杂度:O(1),原地修改,无额外空间开销

关键优势:从后往前复写避免了元素覆盖问题,保证了原数组元素的读取完整性


题目3:快乐数(LeetCode 202)

  1. 题目描述:判断一个数 n 是否为快乐数。

快乐数定义:

  1. 对正整数 n,重复执行操作:将其替换为每个位置上数字的平方和。

  2. 若最终结果为 1,则为快乐数;

  3. 若陷入无限循环(始终无法到1),则不是快乐数。

示例:

输入:19 → 输出:true(过程:1^2+9^2=82 -> 8^2+2^2=68 -> 6^2+8^2=100 -> 1^2+0+0=1)

输入:2 → 输出:false(陷入循环:2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4)

  1. 核心结论

对 n 反复执行平方和操作,结果必然陷入循环(鸽巢原理:取值范围有限,必重复)。

循环只有两种结局:1) 循环终点为 1 → 快乐数;2) 循环终点非 1 → 非快乐数。

因此可用快慢指针(龟兔赛跑算法)检测循环,无需额外空间存储历史值。

快慢指针(Floyd判圈算法)

(1)核心原理

用两个指针遍历序列:

慢指针(slow):每次走 1 步(执行1次平方和操作);

快指针(fast):每次走 2 步(执行2次平方和操作)。

若序列存在环:

快慢指针必然相遇(快指针最终套圈追上慢指针;若相遇点值为 1 → 是快乐数;否则 → 不是快乐数。

(2)适用场景

检测环形结构(链表、数组循环、快乐数循环等);

无需额外空间(O(1) 空间复杂度),时间复杂度 O(\log n)(每次操作位数减少)。

cpp 复制代码
class Solution 
{
public:
    int bitSum(int n) // 返回 n 这个数每⼀位上的平⽅和
    {
        int sum = 0;
        while(n)
        {
            int t = n % 10;
            sum += t * t;
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) 
    {
        int slow = n , fast = bitSum(n);
        while(slow != fast)
        {
            slow = bitSum(slow);
            fast = bitSum(bitSum(fast));
        }
        return slow == 1;
    }
};

关键注意事项

  1. 快慢指针初始值:fast = bitSum(n),而非 fast = n,避免初始时 slow == fast 直接退出循环;

  2. 循环终止条件:slow != fast,相遇即退出,保证效率;

  3. 平方和计算:必须正确提取每一位,避免漏算/错算(如 100 需计算 1^2+0+0=1);

  4. 环的判断:快乐数的核心是"是否以1为环的入口",快慢指针相遇时只需判断是否等于1。


题目4: 盛最多水的容器(LeetCode 11)

  1. 题目描述

给定长度为 n 的整数数组 height,数组中的每个元素代表一条垂直线的高度。找出两条线,使得它们与 x 轴共同构成的容器能容纳最多的水。

核心限制:容器不能倾斜,水的容量由短板决定。

容量公式:V = (j - i) * min(height[i], height[j]),其中 i, j 为两条线的下标。

  1. 示例
  1. 算法思路对比

1) 解法一:暴力求解(超时)

思路:枚举所有可能的两条线组合 (i, j) (i < j),计算每一个组合的容量并记录最大值。

代码逻辑:双层 for 循环。 外层循环:i 从 0 遍历到 n-1。内层循环:j 从 i+1 遍历到 n-1。

计算:min(height[i], height[j]) * (j - i)。

复杂度:O(n^2),O(1)。

缺点:时间复杂度极高,对于 n > 10^4 的数据会超时,不推荐。

cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        int ret = 0;
        // 两层循环枚举所有组合
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                // 计算当前容量
                int v = min(height[i], height[j]) * (j - i);
                ret = max(ret, v);
            }
        }
        return ret;
    }
};

2) 解法二:对撞指针/快慢指针(最优)

核心思想:利用双指针从数组两端向中间逼近,通过贪心策略舍弃无效组合,仅保留可能产生最优解的组合。

指针定义:

left:左指针,初始指向数组左端(0)。

right:右指针,初始指向数组右端(n-1)。

ret:记录最大容量。

移动规则:

  1. 计算当前 left 和 right 构成的容量,更新最大值。

  2. 舍弃短板:移动指向较矮高度的指针。如果 height[left] < height[right],left++。 否则,right--

  3. 循环直到 left >= right。

为什么移动短板有效?

容器的高度由短板决定。如果固定短板,移动长板,宽度减小,高度不变或变小,容量必然减小。

移动短板,虽然宽度减小,但新的短板可能变高,容量有机会增大。

cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0;
        int right = height.size() - 1;
        int ret = 0;
        
        while (left < right) {
            // 计算当前容量
            int v = min(height[left], height[right]) * (right - left);
            ret = max(ret, v);
            
            // 移动指针:谁矮谁走
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return ret;
    }
};

|------|---------|-------|---------|
| 算法 | 时间复杂度 | 空间复杂度 | 评价 |
| 暴力求解 | O(n^2) | O(1) | 效率极低,超时 |
| 对撞指针 | O(n) | O(1) | 最优,一次遍历 |

注意点

  1. 容量计算:必须使用 min(height[left], height[right]),水不会溢出短板。

  2. 指针移动:必须移动短板。若移动长板,宽度减小且高度受限于短板,容量只会变小,无搜索意义。

  3. 初始值:left 初始为 0,right 初始为 height.size() - 1,保证初始宽度最大。


题目5:有效三角形的个数LeetCodLL(LeetCode 611)

题目描述

给定非负整数数组 nums,统计其中能组成三角形三条边的三元组个数。

三角形判定定理:任意两边之和大于第三边 → 优化为:较小的两边之和 > 最大边(其余两个不等式自动成立)。

解法一:暴力枚举(会超时)

算法思路

  1. 先排序:将数组从小到大排序,方便后续判断(只需验证 nums[i] + nums[j] > nums[k],其中 i<j<k,nums[k] 为最大边)。

  2. 三层循环枚举:遍历所有 i<j<k 的三元组,满足条件则计数+1。

时间复杂度:O(n^3),数据量稍大就会超时,仅作为基础思路参考。

代码(C++)

cpp 复制代码
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        // 1. 排序
        sort(nums.begin(), nums.end());
        int n = nums.size(), ret = 0;
        // 2. 从小到大枚举所有三元组
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    // 当最小的两个边之和大于第三边时,统计答案
                    if (nums[i] + nums[j] > nums[k])
                        ret++;
                }
            }
        }
        return ret;
    }
};

解法二:排序 + 双指针(对撞指针,最优解)

算法思路

  1. 先排序:数组从小到大排序,固定最大边 nums[i](从数组末尾向前遍历)。

  2. 双指针统计:在 [0, i-1] 区间内,用 left 指向区间左端点,right 指向区间右端点:

若 nums[left] + nums[right] > nums[i]:说明 [left, right-1] 所有元素都能和 nums[right] 组成满足条件的二元组,共 right-left 个,计数后 right--。

若 nums[left] + nums[right] ≤ nums[i]:说明 nums[left] 无法和任何元素组成满足条件的二元组,left++。

时间复杂度:排序 O(nlog n) + 双指针遍历 O(n^2),整体 O(n^2),可通过所有测试用例。

代码(C++)

cpp 复制代码
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        // 1. 排序
        sort(nums.begin(), nums.end());
        // 2. 利用双指针解决问题
        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])
                {
                    ret += right - left;
                    right--;
                }
                else
                {
                    left++;
                }
            }
        }
        return ret;
    }
};

题目6:和为s的两个数字(剑指 Offer 57,LeetCode LCR 179)

题目核心

输入递增排序的数组和目标值 s,找出数组中和为 s 的两个数,输出任意一对即可。

解法一:暴力枚举(会超时)

算法思路:两层 for 循环枚举所有数对,判断和是否等于目标值。

优化:第二层循环从 i+1 开始,避免重复枚举。

时间复杂度:O(n^2),数组长度较大时超时,仅作基础思路。

代码(C++)

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int n = nums.size();
        for (int i = 0; i < n; i++) { // 第一层循环从前往后列举第一个数
            for (int j = i + 1; j < n; j++) { // 第二层循环从 i 位置之后列举第二个数
                if (nums[i] + nums[j] == target) // 两个数的和等于目标值,找到结果
                    return {nums[i], nums[j]};
            }
        }
        return {-1, -1};
    }
};

解法二:排序 + 双指针(对撞指针,最优解)

算法思路

利用数组升序的特性,用对撞指针优化时间复杂度:

  1. 初始化 left=0(数组左端),right=n-1(数组右端)。

  2. 循环判断两数之和:

和 == 目标值:直接返回这两个数。

和 < 目标值:left++(nums[left] 无法和任何数凑出目标值,舍去)。

和 > 目标值:right--(nums[right] 无法和任何数凑出目标值,舍去)。

时间复杂度:O(n),仅需一次遍历,效率极高。

代码(C++)

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

{nums[left], nums[right]} 这是 C++11 及以后支持的列表初始化语法:

用大括号 {} 把两个元素包裹起来,直接构造一个 vector<int> 类型的对象

等价于手动写 vector<int>{nums[left], nums[right]} 或者 vector<int>({nums[left], nums[right]})

因为函数的返回值类型就是 vector<int>,所以可以直接用这种简洁的写法返回

C++ 函数返回 vector<int> 时,支持用初始化列表直接构造返回值,这是 C++11 引入的语法糖,让代码更简洁。如果是旧版本 C++,需要写成如下,效果完全一致,只是写法更繁琐。

cpp 复制代码
vector<int> res;
res.push_back(nums[left]);
res.push_back(nums[right]);
return res;

题目7:三数之和(LeetCode 15)

  1. 题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

示例1

输入: nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]]

解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。

nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。

nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。

不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。注意,输出的顺序和三元组的顺序并不重要。

示例2:输入: nums = [0,1,1] 输出: [] 解释: 唯一可能的三元组和不为 0 。
示例3:输入: nums = [0,0,0] 输出: [[0,0,0]] **解释:**唯一可能的三元组和为 0 。

  1. 解法:排序 + 双指针(最优)

算法思路:将 三数之和 降维为 两数之和,利用排序和双指针优化,并处理所有去重逻辑。

  1. 排序:先对数组排序,为双指针和去重奠定基础。

  2. 固定数 a:遍历数组,固定第一个数 nums[i](记为 a)。

小优化:如果 nums[i] > 0,因为数组已排序,后面所有数都大于0,不可能和为0,直接 break。

  1. 双指针找 b + c:在 i 后面的区间 [i+1, n-1] 内,用 left 指向左端点,right 指向右端点,找 nums[left] + nums[right] == -a。

  2. 核心去重:

去重 a:如果当前数和前一个数相同,跳过,避免重复结果。

去重 b, c:找到满足条件的数对后,跳过 left 和 right 指向的重复元素,防止记录重复三元组。

代码逻辑解析(C++)

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        vector<vector<int>> ret;
        
        for (int i = 0; i < n; ) {
            // 优化:如果第一个数大于0,不可能组成和为0的三元组
            if (nums[i] > 0) break;
            
            int left = i + 1, right = n - 1;
            int 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和right的重复元素
                    left++;
                    right--;
                    while (left < right && nums[left] == nums[left - 1]) left++;
                    while (left < right && nums[right] == nums[right + 1]) right--;
                }
            }
            
            // 跳过i的重复元素
            i++;
            while (i < n && nums[i] == nums[i - 1]) i++;
        }
        
        return ret;
    }
};
  1. 复杂度分析

1) 时间复杂度

排序:sort(nums.begin(), nums.end()) 的时间复杂度为 O(n\log n)。

外层循环:遍历数组中的每个元素 i,时间复杂度为 O(n)。

内层双指针:对于每个 i,left 和 right 最多遍历整个数组一次,时间复杂度为 O(n)。

总时间复杂度:O(nlog n) + O(n) * O(n) = O(n^2)。

2) 空间复杂度

排序的空间开销:C++ 的 sort 函数使用的是快速排序,空间复杂度为 O(log n)(递归栈开销)。

结果存储:ret 存储的三元组数量最多为 O(n^2)(极端情况下),但题目中要求不重复,实际数量远小于这个值。

总空间复杂度:O(log n) + O(k)(k 为结果中三元组的数量),通常认为空间复杂度为 O(log n)(忽略结果存储的空间)。

  1. 用 set 去重

利用set的元素唯一性特性,将生成的三元组存入set中自动去重,再转成vector返回。

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        set<vector<int>> s; // 利用set自动去重
        
        for (int i = 0; i < n; i++) {
            if (nums[i] > 0) break;
            
            int left = i + 1, right = n - 1;
            int target = -nums[i];
            
            while (left < right) {
                int sum = nums[left] + nums[right];
                if (sum > target) {
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    // 插入set自动去重
                    s.insert({nums[i], nums[left], nums[right]});
                    left++;
                    right--;
                }
            }
        }
        
        // 将set转为vector
        return vector<vector<int>>(s.begin(), s.end());
    }
};

set 会根据 vector 的字典序来比较元素,自动判断是否重复。如果这个 vector 已经存在于 set 中,insert 操作会自动跳过,从而实现去重。

set 的去重依赖于元素的 < 运算符:

对于 vector<int>,比较规则是字典序:从第一个元素开始比较,直到找到第一个不同的元素。

如果两个 vector 的所有元素都相同,就会被判定为重复,不会被插入。


题目8:四数之和(LeetCode 18)

  1. 题目核心

给你一个由 n 个整数组成的数组 nums 和一个目标值 target。请你找出并返回所有满足条件且不重复的四元组:四个索引 a, b, c, d 互不相同;nums[a] + nums[b] + nums[c] + nums[d] == target。

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

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

  1. 解法:排序 + 双指针(嵌套)

算法思路:在三数之和的基础上,再嵌套一层循环,将 四数之和 降维为 三数之和。

  1. 排序:数组排序。

  2. 双层循环固定 a, b:

第一层循环固定第一个数 nums[i](记为 a),并去重。

第二层循环在 i+1 之后固定第二个数 nums[j](记为 b),并去重。

  1. 双指针找 c + d:在 j 后面的区间 [j+1, n-1] 内,用 left 和 right 找 nums[left] + nums[right] == target - nums[i] - nums[j]。

  2. 三级去重:对 a、b、(c, d) 都进行去重操作。

代码逻辑解析(C++)

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();

		// 固定第一个数 a
		for (int i = 0; i < n;)
		{
			// 固定第二个数 b
			for (int j = i + 1; j < n;)
			{
				int left = j + 1, right = n - 1;
				// 目标:c + d = target - a - b,用 long long 防溢出
				long long aim = (long long)target - nums[i] - nums[j];

				// 双指针找 c、d
				while (left < right)
				{
					long long sum = nums[left] + nums[right];
					if (sum > aim)
						right--;
					else if (sum < aim)
						left++;
					else
					{
						// 找到一组解,存入结果
						ret.push_back({nums[i], nums[j], nums[left], nums[right]});
						left++;
						right--;

						// 去重:跳过相同的 left、right
						while (left < right && nums[left] == nums[left - 1])
							left++;
						while (left < right && nums[right] == nums[right + 1])
							right--;
					}
				}

				// 去重 j:跳过相同的第二个数
				j++;
				while (j < n - 1 && nums[j] == nums[j - 1])
					j++;
			}

			// 去重 i:跳过相同的第一个数
			i++;
			while (i < n - 1 && nums[i] == nums[i - 1])
				i++;
		}
		return ret;
	}
};

先排序:双指针 + 去重的基础;固定 2 个数:把四数之和转成两数之和;long long 防溢出:int 相加会爆范围;三处去重:i、j、left/right 都要跳重复

  1. 复杂度分析

1) 时间复杂度

排序:sort(nums.begin(), nums.end()) 的时间复杂度是 O(n log n)。

三层循环:

外层循环(i):遍历数组,最多执行 n 次。

中层循环(j):在 i 的基础上遍历,最多执行 n 次。

内层双指针(left, right):在 j 的基础上遍历,最多执行 n 次。

三层循环的总时间复杂度为 O(n³)。 总时间复杂度:排序的 O(n log n) 可以忽略,最终为 O(n³)。

2) 空间复杂度

结果存储:ret 存储最终的四元组,最坏情况下的空间复杂度为 O(n²)(当所有元素都能组成四元组时)。

排序的空间开销:标准库的 sort 函数通常使用 O(log n) 的栈空间。

总空间复杂度:主要由结果存储决定,为 O(n²)(不计入结果存储的话,为 O(log n))。

  1. 用 set 去重

用 set 实现四数之和的去重,核心思路是利用 set 自动去重的特性,把找到的四元组存入 set,最后再转成 vector 返回。

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ret;
        set<vector<int>> s; // 用于去重的set
        int n = nums.size();
        if (n < 4) return ret;
        
        sort(nums.begin(), nums.end());
        
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int left = j + 1;
                int right = n - 1;
                long long aim = (long long)target - nums[i] - nums[j];
                
                while (left < right) {
                    long long sum = nums[left] + nums[right];
                    if (sum > aim) {
                        right--;
                    } else if (sum < aim) {
                        left++;
                    } else {
                        // 将四元组插入set自动去重
                        s.insert({nums[i], nums[j], nums[left], nums[right]});
                        left++;
                        right--;
                    }
                }
            }
        }
        
        // 将set中的元素转成vector返回
        for (auto& vec : s) {
            ret.push_back(vec);
        }
        
        return ret;
    }
};

✅ 优点:逻辑简单,不需要手动写复杂的去重逻辑,不容易出错。

❌ 缺点:set 的插入和查找有一定的时间开销,效率略低于手动去重的双指针法。

相关推荐
风向决定发型丶2 小时前
Java 线程池 vs Go GMP
java·开发语言·golang
数据与后端架构提升之路2 小时前
系统架构设计师常见高频考点总结之信息化基础与系统规划、项目管理
笔记
诸神缄默不语2 小时前
论文阅读笔记:Claude如何思考
论文阅读·笔记·大模型·llm·大语言模型·claude·大规模预训练语言模型
zzb15802 小时前
Agent案例-智能文档问答助手
java·人工智能·笔记·python
Mr_Xuhhh2 小时前
LeetCode hot 100(C++版本)
c++·leetcode·哈希算法
_kerneler2 小时前
LUKS学习笔记(1)
网络·笔记·学习
cccyi72 小时前
【C++ 脚手架】cpp-httplib 与 websocketpp 库的介绍与使用
c++·websocket·http
EQ-雪梨蛋花汤2 小时前
【存档笔记】三阶贝塞尔 vs 赫米特曲线:原理、公式与工程统一理解
笔记
故事和你912 小时前
洛谷-入门6-函数与结构体
开发语言·数据结构·c++·算法·动态规划