← 返回题目列表

四数之和怎么从三数之和扩展?剪枝和去重应该怎么写?

高频 中等 第 8 / 27 题 更新于 2026/08/03
双指针排序去重

简化版

四数之和通常先排序,再固定前两个数,把问题降成有序数组上的两数之和。

去重要发生在每一层:第一个数去重、第二个数去重、左右指针命中后也要跳过重复值。

如果当前最小和已经大于目标,或者当前最大和仍小于目标,可以提前剪枝。

详细版

四数之和的基本思路是“排序 + 两层枚举 + 双指针”。

先把数组升序排序。外层枚举 i,内层枚举 j,剩下的区间 [j + 1, n - 1] 用左右指针找 target - nums[i] - nums[j]

当四数和等于目标时,记录答案,并同时移动 leftright,跳过重复元素。

当四数和小于目标时,说明需要更大的数,移动 left;当四数和大于目标时,移动 right

复杂度是 O(n^3),空间复杂度不算输出是 O(1)。排序后去重能保证结果中没有重复四元组。

完整版教学

一、为什么四数之和不是直接四层循环

暴力做法枚举 4 个下标,复杂度是 O(n^4)。当 n = 200 时,组合数量大约是 200^4 = 1,600,000,000 级别,面试里通常不可接受。四数之和的突破口是排序后保留有序性,把最后两层循环变成一次线性双指针扫描。这样外面两层是 O(n^2),里面双指针是 O(n),总复杂度降到 O(n^3)

记忆钩子:K 数之和的常见降维方式是“固定若干个数,把最后 2 个数交给双指针”。

二、排序带来了什么能力

排序不是为了好看,而是为了让左右指针有明确移动方向。若当前和太小,移动左指针能让和变大;若当前和太大,移动右指针能让和变小。没有排序时,指针移动没有单调依据,不能保证不会漏解。

当前四数和目标关系移动方式原因
sum < target偏小left++需要更大的第三/第四个数
sum > target偏大right--需要更小的第三/第四个数
sum == target命中两边都移动当前组合已记录,需要找新组合

这个表就是双指针正确性的来源。

三、完整流程怎么拆

可以把算法拆成 3 层:

第 1 层:枚举 i,固定第一个数
第 2 层:枚举 j,固定第二个数
第 3 层:left/right 在剩余区间里找两数之和

代码骨架如下:

function fourSum(nums, target) {
  nums.sort((a, b) => a - b)
  const ans = []
  const n = nums.length
  for (let i = 0; i < n - 3; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) continue
    for (let j = i + 1; j < n - 2; j++) {
      if (j > i + 1 && nums[j] === nums[j - 1]) continue
      let left = j + 1
      let right = n - 1
      while (left < right) {
        const sum = nums[i] + nums[j] + nums[left] + nums[right]
        if (sum === target) {
          ans.push([nums[i], nums[j], nums[left], nums[right]])
          left++
          right--
          while (left < right && nums[left] === nums[left - 1]) left++
          while (left < right && nums[right] === nums[right + 1]) right--
        } else if (sum < target) {
          left++
        } else {
          right--
        }
      }
    }
  }
  return ans
}

面试时不必一开始就背完代码,先讲清楚降维过程更重要。

四、去重为什么要分层处理

四数之和的重复来源有 3 个位置。第一层 i 如果和上一个值相同,固定它得到的组合会重复;第二层 j 同理;命中后左右指针也要跳过相同值,否则同一组数会被反复加入答案。注意去重比较的是值,不是下标,因为题目通常要求“不重复四元组”。

举例:nums = [1,1,1,2,2,3],如果不跳过重复的 i = 1,会产生和 i = 0 一样的开头。命中 [1,1,2,3] 后,如果 left 停在另一个 2 上,也会再次得到同样四元组。

五、剪枝怎么做才安全

排序后可以用最小可能和、最大可能和剪枝。固定 i 后,如果 nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target,后面只会更大,可以直接 break。如果 nums[i] + nums[n-1] + nums[n-2] + nums[n-3] < target,当前 i 太小,可以 continue

同理固定 j 后,也能用后面两个最小值、两个最大值做判断。剪枝要建立在排序基础上,并且不能把可能答案排除掉。

六、常见误区与追问

  • 误区:四数之和直接套三数之和但只去重一层。 四元组重复可能来自 ijleft/right 三个层面,少一层都会出重复答案。
  • 误区:命中后只移动 left。 如果只移动一侧,另一侧重复值可能导致重复记录,也会错过下一组组合。
  • 误区:剪枝条件没有排序依据。 未排序数组上比较最小和、最大和没有意义,剪枝可能误删答案。
  • 追问:复杂度还能低于 O(n^3) 吗? 通用四数之和列出全部结果时通常很难,因为结果规模本身可能很大;哈希能换思路,但去重和空间成本更复杂。
  • 追问:为什么不用 Set 暴力去重? Set 能兜底,但会掩盖去重逻辑,且字符串化四元组有额外开销;面试更看重排序后的结构化去重。

这些问题集中考察“排序带来的单调性”和“多层去重边界”。

七、加强记忆

记四数之和可以抓住“固定两个,双指针两个”。先排序,让和的变化可控;外层固定 ij,每层都跳过重复值;内层用 left/right 根据和偏小或偏大移动。剪枝时只相信排序能证明的最小和和最大和。这样从三数之和扩到四数之和时,核心思想不变,只是多了一层固定和更多去重位置。