题目描述
思路解析
一句话答案:LeetCode 851 喧闹和富有:把边反向建图、让每个人指向更富的邻居,再用记忆化 DFS 求出 i 和所有更富者里最安静的那个,每人只算一次,时间 O(n+m)。
每个人要找『不比他穷的人』里最安静的
n 个人,每人一个互不相同的安静值 quiet[i];richer[i]=[a,b] 表示 a 比 b 有钱。要为每个人 x 求 answer[x]:所有钱不少于 x 的人里,含 x 自己,安静值最小者的编号。题面 richer=[[1,0],[2,1],[3,1],[4,3],[5,3]]、quiet=[3,5,4,2,1,0],答案 [5,5,2,5,4,5]。0 号能牵连到 5 号,5 号安静值 0 最小,所以 answer[0]=5。
每人都从自己出发爬一遍更富的人,慢在哪
给每个人单独算,就是从他出发顺着『更富』一层层爬,收齐能到的人再挑最安静的。可 0 号要爬全部五人、1 号要爬 2、3、4、5,路径大量重叠,同一片子图反复走,次数从线性涨到指数。
反向建图,让每个人指向更富的邻居
重复全在每次重新爬,得让算过的结果存下来复用。题目说『a 比 b 富』,我们反着存:把边掉个方向,从穷的 b 连一条指向更富的 a,这样从谁出发顺箭头走到的都是钱不比他少的人。于是 answer[i] 就是 i 自己和每个更富邻居各自答案里最安静的一个——邻居的答案已涵盖它能牵连的更富者,只看直接邻居就够。
dfs(i) 先算完更富的邻居再回头比
代码是一个带记忆化的 dfs,即先钻进一个更富邻居、把那一支算完就看下一个。记忆化,就是算过的结果存下来不重算:ans 数组全填 -1 当『没算过』记号,ans[i] 不是 -1 就直接返回。dfs(i) 先把 ans[i] 记成自己,因为钱不少于自己的至少有本人;再遍历更富邻居 j,先递归 dfs(j) 算完那一支,只要 quiet[ans[j]]<quiet[ans[i]] 就把 ans[i] 换成 ans[j]。外层对每人调一次 dfs,真正展开只有第一次。
六个人这张图,答案怎么一趟全定下来
跟着题面走。反向建图后 g[0]=[1]、g[1]=[2,3]、g[3]=[4,5],2、4、5 号没有出边。dfs(2) 无邻居,ans[2]=2;回到 1 号,quiet[2]=4<quiet[1]=5,1 号暂定 2 号。dfs(3) 先钻 4,ans[4]=4,quiet[4]=1<quiet[3]=2;再钻 5,ans[5]=5,quiet[5]=0<quiet[4]=1,得 ans[3]=5。回到 1 号,quiet[5]=0<quiet[2]=4,ans[1]=5;回到 0 号,quiet[5]=0<quiet[0]=3,ans[0]=5。六人一趟全定好,其余全命中缓存直接返回,凑出 [5,5,2,5,4,5]。
复杂度是线性的,三个坑别踩
时间 O(n+m):建图是 O(m),记忆化让每人只算一次、每条边只走一次;空间 O(n+m),邻接表、答案数组加最坏一条长链的递归栈。三处最容易写坏:箭头得从穷指向富,指错了会滑到更穷的人、编号全错;别省记忆化,漏了那句提前返回,顶层会把下面整片重复展开,复杂度退回指数;别被『富有』带偏,要的是最安静而非最有钱。没有更富关系时答案是自己,走到头的节点也是自己。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class Solution: def loudAndRich(self, richer: List[List[int]], quiet: List[int]) -> List[int]: def dfs(i: int): if ans[i] != -1: return ans[i] = i for j in g[i]: dfs(j) if quiet[ans[j]] < quiet[ans[i]]: ans[i] = ans[j] g = defaultdict(list) for a, b in richer: g[b].append(a) n = len(quiet) ans = [-1] * n for i in range(n): dfs(i) return ans复杂度
- 时间:O(n + m),n 是人数,m 是 richer 的对数。建图遍历 m 对是 O(m);深度优先靠记忆化,每个人的答案只真正计算一次,每条边也只被走一次,合计 O(n + m)
- 空间:O(n + m),邻接表存全部边是 O(n + m);答案数组 O(n);递归调用栈最坏(关系连成一条长链)深度到 O(n)
易错点
面试追问把动画讲成自己的话
追问这题除了 DFS 加记忆化,还有别的解法吗?
追问为什么这题的图一定不会有环,可以放心递归?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
翻转矩阵后的得分
LeetCode 861 · 中等 · 沿着 图论套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题