【算法拾光】双指针|三数之和 + 四数之和

对撞指针经典进阶组合:三数之和、四数之和。属于面试必考题。两道题核心思路一致:排序 + 固定外层数字 + 双指针寻找剩余值 ,难点不在于双指针本身,而在于去重逻辑,很多人代码能跑,但重复结果处理不干净。

前言

两数之和(有序版)是对撞指针入门;三数、四数之和,就是把「多枚举一层 + 双指针」结合。 暴力做法:三数 \(O(n^3)\),四数 \(O(n^4)\),大数据直接超时。 优化思路:先排序,固定前面 1/2 个数,剩下部分用左右对撞指针查找目标,把复杂度降下来。 两道题共同难点:跳过重复元素,避免输出重复的答案组合。


题目 1:三数之和15. 三数之和 - 力扣(LeetCode)

题目描述

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

注意:答案中不可以包含重复的三元组。

示例:

cpp 复制代码
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

思路分析

  1. 数组先升序排序,方便双指针移动 + 去重。
  2. 外层循环固定第一个数 nums[i]
    • 如果 nums[i]>0:后面所有数字都≥nums i,三数之和必然大于 0,直接 break 结束循环。
    • 去重 :如果当前 nums[i] 和上一个数字相等,直接 continue,跳过重复,防止产生一样的三元组。
  3. 在 i 的右侧区间 [i+1, n-1] 设置对撞指针:
    • left = i + 1,right = n - 1
    • 求和 sum = nums[i] + nums[left] + nums[right]
      • sum == 0:找到一组解,加入结果集;左右指针都要去重,跳过相同值;left++, right--
      • sum < 0:总和偏小,left++,增大总和
      • sum > 0:总和偏大,right--,减小总和

核心坑点:三处去重(i 去重、left 去重、right 去重),很多新手只写一层去重,导致答案重复。

C++ 代码

cpp 复制代码
vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> res;
    sort(nums.begin(), nums.end());
    int n = nums.size();
    for(int i = 0; i < n; i++){
        // 第一个数大于0,不可能凑出和为0
        if(nums[i] > 0) break;
        // 对i去重
        if(i > 0 && nums[i] == nums[i-1]) continue;

        int left = i + 1;
        int right = n - 1;
        while(left < right){
            int sum = nums[i] + nums[left] + nums[right];
            if(sum == 0){
                res.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--;
                left++;
                right--;
            }else if(sum < 0){
                left++;
            }else{
                right--;
            }
        }
    }
    return res;
}

复杂度分析

  • 时间复杂度:排序 \(O(n\log n)\);外层循环\(O(n)\),内层双指针\(O(n)\);整体 \(O(n^2)\)
  • 空间复杂度:\(O(\log n)\),排序栈开销(不计结果数组)

题目 2:四数之和18. 四数之和 - 力扣(LeetCode)

题目描述

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复 的四元组 [nums[a], nums[b], nums[c], nums[d]]:

  • \(0 \le a < b < c < d < n\)
  • \(numsa + numsb + numsc + numsd = target\)

注意:答案中不可以包含重复的四元组。

示例:

cpp 复制代码
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

思路分析

三数之和是固定 1 层,四数之和固定两层,剩下依旧用左右对撞指针。

  1. 数组升序排序。
  2. 第一层循环固定 nums[i],i 去重
  3. 第二层循环固定 nums[j](j 起始为 i+1),j 去重
  4. 在区间 [j+1, n-1] 设置对撞指针 left、right
    • sum = numsi+numsj+numsleft+numsright
    • sum == target:记录答案,left、right 分别去重;left++, right--
    • sum < target:left++
    • sum > target:right--

重点:四数之和不能直接用三数之和那种 nums[i]>target 剪枝!因为存在负数相加变小,剪枝条件要小心,容易剪错。

C++ 代码

cpp 复制代码
vector<vector<int>> fourSum(vector<int>& nums, int target) {
    vector<vector<int>> res;
    sort(nums.begin(), nums.end());
    int n = nums.size();
    for(int i = 0; i < n; i++){
        // i去重
        if(i > 0 && nums[i] == nums[i-1]) continue;
        for(int j = i + 1; j < n; j++){
            // j去重
            if(j > i + 1 && nums[j] == nums[j-1]) continue;
            int left = j + 1;
            int right = n - 1;
            while(left < right){
                // 注意long long防止int溢出
                long long sum = (long long)nums[i] + nums[j] + nums[left] + nums[right];
                if(sum == target){
                    res.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--;
                    left++;
                    right--;
                }else if(sum < target){
                    left++;
                }else{
                    right--;
                }
            }
        }
    }
    return res;
}

⚠️ 重要细节:四个 int 相加可能溢出,强制转 long long,笔试高频坑!

复杂度分析

  • 时间复杂度:排序 \(O(n\log n)\);两层循环 \(O(n^2)\),内层双指针\(O(n)\);整体 \(O(n^3)\)
  • 空间复杂度:\(O(\log n)\),排序栈开销(不计结果数组)

📌 两道题对比总结(博客重点)

表格

题目 固定层数 指针模型 核心难点 溢出风险
三数之和 固定 1 层 (i) left、right 对撞 i/left/right 三处去重 较小,一般不会溢出
四数之和 固定 2 层 (i,j) left、right 对撞 i/j/left/right 四处去重;剪枝陷阱 极易 int 溢出,必须 long long

共同点:

  1. 先排序,再固定外层元素,剩余区间使用对撞指针;
  2. 去重逻辑是两道题最大考点;
  3. 本质是暴力枚举的优化,把高次复杂度降一档。

通用扩展思路:k 数之和 k=2:有序数组直接双指针; k>2:固定 k-2 层循环,最后两个数用对撞指针;k 越大,复杂度越高。

小结

三数之和是对撞指针的分水岭,很多人能看懂双指针移动,但写不对去重。四数之和就是三数之和的套娃升级,多一层循环、多一处去重,额外增加整数溢出陷阱。 掌握这两道,你就掌握了「k 数之和」的通用解题框架。面试时如果被问到 k 数之和,可以直接把这套思路讲出来,加分项。

相关推荐
我爱工作&工作love我1 小时前
P14359 [CSP-J 2025] 异或和
算法
茉莉玫瑰花茶1 小时前
GO [ 数据格式 ]
服务器·网络·算法·golang
小poop1 小时前
高级排序算法与性能对比——归并、非比较排序及总结
数据结构·算法·排序算法
aqiu1111111 小时前
CodeTop按顺序刷题0003:LeetCode 206. 反转链表(迭代法图文详解与动态演示)
算法·leetcode·链表
果霸大叔1 小时前
AI网关第8篇:AI 代理与负载均衡--多模型实例如何调度?
人工智能·算法·自然语言处理·负载均衡·ai-native
不会就选b2 小时前
算法日常・每日刷题--<动态规划>29
数据结构·算法·leetcode
垆边人似月.2 小时前
最少的元素个数使和为 target
数据结构·c++·算法
炽橙超真云2 小时前
2D 工程图纸如何自动转成可编辑 3D 模型?拆解读图与建体两条技术路径(DXF/DWG/PDF 到 STEP)
算法·3d·工业智能·工业ai
夕除2 小时前
redis--dict rehash
算法·哈希算法