LeetCode 454 - 四数相加 II


文章目录

摘要

LeetCode 454 是一道非常典型的 "用空间换时间" 的题。

如果你第一次看这道题,很容易写出一个四重循环,然后立刻发现:
完了,直接超时。

但这题真正考的不是暴力,而是你能不能意识到一件事:

四个数的和为 0,其实可以拆成 两部分的和互相抵消

一旦你从 A + B + C + D = 0 转成
(A + B) = -(C + D)

这道题的复杂度立刻从"不可做"变成了"非常稳"。

描述

题目给了你四个长度相同的整数数组:

  • nums1
  • nums2
  • nums3
  • nums4

要求你统计有多少个四元组 (i, j, k, l),满足:

text 复制代码
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

约束信息很关键

  • 每个数组长度 n <= 200
  • 数值范围是 [-2^28, 2^28]

这意味着什么?

  • 四重循环是 O(n^4),最大是 200^4,根本跑不完
  • 必须降到 O(n^2) 级别

题解答案(核心思路)

关键拆分思路

把四个数组拆成两组:

  • 第一组:nums1 + nums2
  • 第二组:nums3 + nums4

目标条件:

text 复制代码
a + b + c + d = 0

等价于:

text 复制代码
(a + b) = -(c + d)

整体策略

  1. 枚举 nums1nums2 的所有和,记录每个和出现的次数
  2. 枚举 nums3nums4 的所有和
  3. 对于每个 (c + d),查表看有没有 -(c + d)
  4. 累加出现次数

这一步的本质是:
把四数问题,降维成两个"两数之和"的问题。

题解答案(Swift 可运行 Demo)

swift 复制代码
class Solution {
    func fourSumCount(
        _ nums1: [Int],
        _ nums2: [Int],
        _ nums3: [Int],
        _ nums4: [Int]
    ) -> Int {
        
        var sumMap: [Int: Int] = [:]
        
        // 1. 统计 nums1 + nums2 的所有可能和
        for a in nums1 {
            for b in nums2 {
                let sum = a + b
                sumMap[sum, default: 0] += 1
            }
        }
        
        var result = 0
        
        // 2. 枚举 nums3 + nums4,寻找补数
        for c in nums3 {
            for d in nums4 {
                let target = -(c + d)
                if let count = sumMap[target] {
                    result += count
                }
            }
        }
        
        return result
    }
}

题解代码分析

1. 为什么要用字典?

swift 复制代码
var sumMap: [Int: Int] = [:]

这里的字 considered 是:

  • key:nums1[i] + nums2[j]
  • value:这个和出现的次数

因为:

  • 同一个和可能来自不同下标组合
  • 每一种组合都要算进答案

2. 第一阶段:构建和的"频率表"

swift 复制代码
for a in nums1 {
    for b in nums2 {
        let sum = a + b
        sumMap[sum, default: 0] += 1
    }
}

这一段做的事情很单纯:

  • 枚举所有 (i, j)
  • a + b 当成一个"中间结果"缓存起来

这一步的复杂度是:

text 复制代码
O(n²)

3. 第二阶段:查补数并累加

swift 复制代码
let target = -(c + d)
if let count = sumMap[target] {
    result += count
}

这里非常关键的一点是:

  • 不是 +1
  • 而是 +count

原因是:

  • 可能有多个 (a, b) 对应同一个 sum
  • 每一种都能和当前 (c, d) 组成一个合法四元组

4. 为什么这样不会漏算或重复算?

因为:

  • (a, b) 只在第一阶段统计
  • (c, d) 只在第二阶段枚举
  • 每个合法组合刚好被计算一次

示例测试及结果

示例 1

swift 复制代码
let solution = Solution()

let nums1 = [1, 2]
let nums2 = [-2, -1]
let nums3 = [-1, 2]
let nums4 = [0, 2]

print(solution.fourSumCount(nums1, nums2, nums3, nums4))

输出:

text 复制代码
2

示例 2

swift 复制代码
print(solution.fourSumCount([0], [0], [0], [0]))

输出:

text 复制代码
1

自定义测试

swift 复制代码
print(solution.fourSumCount([1, -1], [-1, 1], [0], [0]))

逻辑上:

text 复制代码
(1 + -1) + (0 + 0) = 0
(-1 + 1) + (0 + 0) = 0

输出:

text 复制代码
2

实际场景结合

这道题的思想在真实业务里非常常见。

1. 多条件组合统计

比如:

  • 用户行为 A
  • 用户行为 B
  • 用户行为 C
  • 用户行为 D

你想统计:
满足某个组合约束的用户数量

直接全量组合几乎一定炸。

2. 典型的"中间结果缓存"

  • 把复杂问题拆成两半
  • 把一半的结果预先算好并缓存
  • 用另一半去查表

这是很多高性能系统的基本套路。

3. 面试中的信号题

这道题非常适合用来区分:

  • 只会写暴力的人
  • 能主动做复杂度分析、拆问题的人

时间复杂度

  • 构建哈希表:O(n²)
  • 查找补数:O(n²)

总时间复杂度:

text 复制代码
O(n²)

空间复杂度

  • 哈希表最多存 个键值对

空间复杂度:

text 复制代码
O(n²)

总结

LeetCode 454 的关键不在代码有多复杂,而在于你是否能意识到:

  • 四数问题 ≠ 四重循环
  • 拆分 + 哈希表 是最稳的解法

如果你在刷题或写博客时,能把这道题讲清楚,基本就已经说明:

你不只是"会写题解",而是真的理解了算法设计背后的思维方式。

相关推荐
想做后端的小C2 小时前
Java:访问权限
java·开发语言
啃火龙果的兔子2 小时前
java语言基础
java·开发语言·python
tokepson2 小时前
反向传播
深度学习·算法·ai·反向传播
禾高网络2 小时前
互联网医院定制|互联网医院|禾高互联网医院搭建
java·大数据·人工智能·小程序
掘根2 小时前
【消息队列项目】消费者管理模块实现
java·开发语言
努力的小郑2 小时前
MyBatis 两个隐蔽深坑实录:Arrays.asList() 与数字 0 的“离奇失踪”
java·面试·mybatis
Xの哲學2 小时前
Linux AQM 深度剖析: 拥塞控制
linux·服务器·算法·架构·边缘计算
故渊ZY2 小时前
SpringMVC核心原理与实战全解析
java·spring
艾醒2 小时前
大模型原理剖析——突破LLM效率瓶颈:多标记预测(MTP)技术深度解析与实战
算法