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

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

前言

两数之和(有序版)是对撞指针入门;三数、四数之和,就是把「多枚举一层 + 双指针」结合。 暴力做法:三数 \(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 数之和,可以直接把这套思路讲出来,加分项。

相关推荐
其实防守也摸鱼19 小时前
个人学习 Pwn 思路:从基础到漏洞利用的进阶路径
大数据·前端·人工智能·chrome·算法·安全·网络安全
Nuanyt19 小时前
LeetCode Hot 100 链表专题(五)· 2 题(Java)
java·算法·leetcode·链表
w_zero_one1 天前
递归算法(一)
java·开发语言·算法
风影同志783241 天前
20 个比特能装下多少行情?——实测拆解 Kronos 的 K 线分词器
算法
写的都是BUG1 天前
APK 里的钥匙,一直就放在门口:聊聊加固真正该做什么
前端·算法
地平线开发者1 天前
CLIP 的零样本分类:一组文本如何变成图像分类器?
算法
京东云开发者1 天前
从“会生成”到“能生产”:萤火虫 AIGC 平台的 AI Infra 实践
算法·aigc·产品
youshi66681 天前
PID——算法改进
算法
一只不想敲代码的程序猿1 天前
如何更高效地拆解问题?
算法