三数之和的多种可能 图解题解
这道题到底在问什么
- 输入
- arr=[1,2,3,3,3,4,4], target=9
- 输出
- 8
先想最直接的笨办法
记住口诀:先数清每个值几次,再枚举三个值,按相不相等用组合数算搭配。下面每帧都在套它。(动画第 3 步)
最优解:一步一步想明白
- 3记住口诀:先数清每个值几次,再枚举三个值,按相不相等用组合数算搭配。下面每帧都在套它。
- 4先一遍扫数组数清每个值出现几次。现在读到 arr[0] = 1,把它在计数表里的次数加到 1。
- 5先一遍扫数组数清每个值出现几次。现在读到 arr[1] = 2,把它在计数表里的次数加到 1。
- 6先一遍扫数组数清每个值出现几次。现在读到 arr[2] = 3,把它在计数表里的次数加到 1。
- 7先一遍扫数组数清每个值出现几次。现在读到 arr[3] = 3,把它在计数表里的次数加到 2。
- 8先一遍扫数组数清每个值出现几次。现在读到 arr[4] = 3,把它在计数表里的次数加到 3。
- 9先一遍扫数组数清每个值出现几次。现在读到 arr[5] = 4,把它在计数表里的次数加到 1。
- 10先一遍扫数组数清每个值出现几次。现在读到 arr[6] = 4,把它在计数表里的次数加到 2。
- 11计数表全部建好:值 1 有 1 个、2 有 1 个、3 有 3 个、4 有 2 个。接下来枚举三个值 a ≤ b ≤ c,看哪些组合相加正好等于 9。
- 12开始枚举。先看 (1, 1, 2),三个值相加是 4,不等于 9,这组跳过。
- 13再看 (1, 2, 3),和是 6,没到 9,差一点,跳过。
- 14再看 (1, 3, 4),和是 8,没到 9,差一点,跳过。
- 15(1, 4, 4) 三个值相加正好是 9,命中!高亮的就是这几个值所在的「桶」。下一帧算它能凑出多少种下标搭配。
- 16这组是「两值相同」。一个 1 加两个 4:cnt[1] × C(2,2) = 1 × 1 = 1。把这 1 种搭配加进答案,ans 现在是 1。
- 17再看 (2, 2, 4),和是 8,没到 9,差一点,跳过。
- 18(2, 3, 4) 三个值相加正好是 9,命中!高亮的就是这几个值所在的「桶」。下一帧算它能凑出多少种下标搭配。
- 19这组是「三值都不同」。三个值都不同:cnt[2] × cnt[3] × cnt[4] = 1 × 3 × 2 = 6。把这 6 种搭配加进答案,ans 现在是 7。
- 20再看 (2, 4, 4),和是 10,比 9 大,跳过。
- 21(3, 3, 3) 三个值相加正好是 9,命中!高亮的就是这几个值所在的「桶」。下一帧算它能凑出多少种下标搭配。
- 22这组是「三值相同」。三个值都是 3:从 3 个 3 里挑 3 个,组合数 C(3,3) = 1。把这 1 种搭配加进答案,ans 现在是 8。
- 23再看 (3, 3, 4),和是 10,比 9 大,跳过。
- 24这里只展示部分代表性组合,但所有命中 9 的组合都完整演了:三组分别贡献 1、6、1,合计 8。动画用的是「按值枚举 a≤b≤c + 组合数」的直观版:枚举全部三值组合,cnt 不足或和不等于 9 的直接跳过。下一帧 step 25 的参考代码则是等价的「下标版」(枚举中间下标 j、左侧下标 i,再用 cnt 查右侧数量),两者结果一致,但复杂度表达不同:动画按值枚举版是 O(n + K²),参考代码下标版是 O(n²)。这就是满足条件的下标元组总数。
⚠️ 容易写错的地方
✗ 错:直接三重循环枚举下标
✓ 对:先计数,再按值组合
数组可达 3000,三重循环 O(n³) 会超时;值域只有 101,按值枚举快得多
✗ 错:两个值相同也用 cnt×cnt 乘
✓ 对:同一个值挑两个要用 C(cnt,2)
cnt×cnt 会把「同一个下标选两次」和「顺序」重复算,组合数 C(cnt,2) 才对
✗ 错:忘了取模或只在最后取一次
✓ 对:每次累加都取模 1e9+7
中间和可能溢出,边加边取模才安全
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def threeSumMulti(self, arr: List[int], target: int) -> int:
mod = 10**9 + 7
cnt = Counter(arr)
ans = 0
for j, b in enumerate(arr):
cnt[b] -= 1
for a in arr[:j]:
c = target - a - b
ans = (ans + cnt[c]) % mod
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int threeSumMulti(vector<int>& arr, int target) {
const int mod = 1e9 + 7;
int cnt[101]{};
for (int x : arr) {
++cnt[x];
}
int n = arr.size();
int ans = 0;
for (int j = 0; j < n; ++j) {
--cnt[arr[j]];
for (int i = 0; i < j; ++i) {
int c = target - arr[i] - arr[j];
if (c >= 0 && c <= 100) {
ans = (ans + cnt[c]) % mod;
}
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int threeSumMulti(int[] arr, int target) {
final int mod = (int) 1e9 + 7;
int[] cnt = new int[101];
for (int x : arr) {
++cnt[x];
}
int n = arr.length;
int ans = 0;
for (int j = 0; j < n; ++j) {
--cnt[arr[j]];
for (int i = 0; i < j; ++i) {
int c = target - arr[i] - arr[j];
if (c >= 0 && c < cnt.length) {
ans = (ans + cnt[c]) % mod;
}
}
}
return ans;
}
}复杂度
时间
O(n + K²)
动画按值枚举:先扫一遍 arr 建 cnt 是 O(n),再枚举不同值种类 K≤101 是 O(K²);参考代码下标版是 O(n²)
空间
O(1)
计数数组大小固定 101(值域 0 到 100)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 三数之和的多种可能 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题和经典 3Sum(两数之和的双指针版)有什么联系和区别?+
联系是都在找三个数凑成目标;区别是经典 3Sum 求「不同的值组合」要去重,本题求「下标元组的数量」要计数。本题值域很小(0 到 100),所以走「计数 + 组合数」最优;如果值域很大,也可以先排序,固定一个数后用双指针,遇到相等区间时按区间长度算组合数。
如果不用组合数,能不能用参考代码那种两层循环?+
可以,而且更好写。外层枚举中间下标 j、固定值 b,扫 j 左边的每个 a,需要的第三个值 c = target − a − b,直接查计数表 cnt[c] 累加;关键是枚举到 j 时先把 cnt[b] 减 1,保证 b 自己不会被当成第三个值重复选。这就是动画思路的下标版实现,结果一致。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 三数之和的多种可能 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。