适龄的朋友 图解题解
这道题到底在问什么
- 输入
- ages=[16,16]
- 输出
- 2 (两人互发)
- 输入
- ages=[20,30,100,110,120]
- 输出
- 3 (110→100,120→110,120→100)
先想最直接的笨办法
回放一遍:全程没有两两枚举,只是按年龄分桶 + 对每个年龄查一次有效区间。年龄种类最多 121 种,所以无论多少人,统计都很快。最终答案 7。(动画第 24 步)
最优解:一步一步想明白
- 3三个条件简化成一个区间:第②条说不能发给比自己大的,第③条其实被第②条包住了(年龄超 100 的一定比小于 100 的大,已经被②挡掉)。所以真正起作用的就是 (0.5·x+7, x]。
- 4先把人按年龄分组:这就是计数桶。上方一排是不同年龄,右侧表是每个年龄各有几个人。接下来对每个年龄当发送方,逐一算它能发出多少请求。
- 5轮到年龄 18 当发送方(紫色)。它只能发给年龄在 (16, 18] 里的人:既要大于 16,又不能超过自己 18。下面逐个桶检查谁落在这个区间里。
- 6检查到自己这组年龄 18:同龄之间互发是允许的,但不能发给自己,所以 2 个人里只能收 1 个(减掉本人)。绿色标记区间内有效。
- 7结算年龄 18 这组:每个人能发 1 条,这组有 2 人,一共贡献 2 条请求。总数累计到 2。
- 8轮到年龄 25 当发送方(紫色)。它只能发给年龄在 (19.5, 25] 里的人:既要大于 19.5,又不能超过自己 25。下面逐个桶检查谁落在这个区间里。
- 9检查年龄 18:它没有超过下界 19.5,会被第①条「ages[y] ≤ 0.5·x+7」挡掉,发不出去(标灰)。
- 10检查到自己这组年龄 25:同龄之间互发是允许的,但不能发给自己,所以 1 个人里只能收 0 个(减掉本人)。绿色标记区间内有效。
- 11年龄 25 这组:有效区间里除了自己没别人可发,每人能发 0 条,这组贡献 0 条。总数仍是 2。
- 12轮到年龄 30 当发送方(紫色)。它只能发给年龄在 (22, 30] 里的人:既要大于 22,又不能超过自己 30。下面逐个桶检查谁落在这个区间里。
- 13检查年龄 18:它没有超过下界 22,会被第①条「ages[y] ≤ 0.5·x+7」挡掉,发不出去(标灰)。
- 14检查年龄 25:它大于下界 22,落在有效区间里(标绿)。这组 1 人每个都能成为接收方。
- 15检查到自己这组年龄 30:同龄之间互发是允许的,但不能发给自己,所以 1 个人里只能收 0 个(减掉本人)。绿色标记区间内有效。
- 16结算年龄 30 这组:每个人能发 1 条,这组有 1 人,一共贡献 1 条请求。总数累计到 3。
- 17轮到年龄 40 当发送方(紫色)。它只能发给年龄在 (27, 40] 里的人:既要大于 27,又不能超过自己 40。下面逐个桶检查谁落在这个区间里。
- 18检查年龄 18:它没有超过下界 27,会被第①条「ages[y] ≤ 0.5·x+7」挡掉,发不出去(标灰)。
- 19检查年龄 25:它没有超过下界 27,会被第①条「ages[y] ≤ 0.5·x+7」挡掉,发不出去(标灰)。
- 20检查年龄 30:它大于下界 27,落在有效区间里(标绿)。这组 1 人每个都能成为接收方。
- 21检查到自己这组年龄 40:同龄之间互发是允许的,但不能发给自己,所以 2 个人里只能收 1 个(减掉本人)。绿色标记区间内有效。
- 22结算年龄 40 这组:每个人能发 2 条,这组有 2 人,一共贡献 4 条请求。总数累计到 7。
- 23把四个年龄组各自贡献的请求数加起来:2 + 0 + 1 + 4 = 7。这就是全网产生的好友请求总数。
- 24回放一遍:全程没有两两枚举,只是按年龄分桶 + 对每个年龄查一次有效区间。年龄种类最多 121 种,所以无论多少人,统计都很快。最终答案 7。
⚠️ 容易写错的地方
✗ 错:把区间下界当成可取(写成 ages[y] ≥ 0.5·x+7)
✓ 对:是严格大于,等于也排除
第①条是 ages[y] ≤ 0.5·x+7 才排除,所以有效是 ages[y] > 0.5·x+7,边界值要被挡掉
✗ 错:同龄统计忘了减自己
✓ 对:ax==ay 时人数要减 1
人不会向自己发请求,cnt[x] 个同龄人里每人只能发给另外 cnt[x]−1 个
✗ 错:以为第③条独立要单独处理
✓ 对:它被第②条完全包含
ages[y] > 100 且 ages[x] < 100 时必有 ages[y] > ages[x],已被第②条排除,可忽略
完整代码(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 numFriendRequests(self, ages: List[int]) -> int:
cnt = [0] * 121
for x in ages:
cnt[x] += 1
ans = 0
for ax, x in enumerate(cnt):
for ay, y in enumerate(cnt):
if not (ay <= 0.5 * ax + 7 or ay > ax or (ay > 100 and ax < 100)):
ans += x * (y - int(ax == ay))
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 numFriendRequests(vector<int>& ages) {
const int m = 121;
vector<int> cnt(m);
for (int x : ages) {
++cnt[x];
}
int ans = 0;
for (int ax = 1; ax < m; ++ax) {
for (int ay = 1; ay < m; ++ay) {
if (!(ay <= 0.5 * ax + 7 || ay > ax || (ay > 100 && ax < 100))) {
ans += cnt[ax] * (cnt[ay] - (ax == ay ? 1 : 0));
}
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int numFriendRequests(int[] ages) {
final int m = 121;
int[] cnt = new int[m];
for (int x : ages) {
++cnt[x];
}
int ans = 0;
for (int ax = 1; ax < m; ++ax) {
for (int ay = 1; ay < m; ++ay) {
if (!(ay <= 0.5 * ax + 7 || ay > ax || (ay > 100 && ax < 100))) {
ans += cnt[ax] * (cnt[ay] - (ax == ay ? 1 : 0));
}
}
}
return ans;
}
}复杂度
时间
O(n + A²)
n 统计人数,A=121 是年龄上限,两层年龄循环与人数无关
空间
O(A)
一个长度 121 的计数桶
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 适龄的朋友 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能不用 121×121,而是排序后用二分或双指针?+
可以。把 ages 排序后,对每个 x 用二分找出有效区间 (0.5·x+7, x] 的左右边界,区间长度就是接收人数(同龄要减自己)。这样是 O(n log n)。计数桶法 O(n + 121²) 在本题年龄范围固定时更直接,两种都被接受。
第③条 ages[y] > 100 且 ages[x] < 100 真的可以忽略吗?+
可以。若 ages[y] > 100 而 ages[x] < 100,那么 ages[y] 一定大于 ages[x],这种情况已经被第②条 ages[y] > ages[x] 排除了。所以第③条不会排除任何「第②条没排除」的配对,纯属冗余。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 适龄的朋友 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。