道路的最大总重要性 图解题解
这道题到底在问什么
- 输入
- n=5, roads=[[0,1],[1,2],[2,3],[0,2],[1,3],[2,4]]
- 输出
- 43
- 输入
- n=5, roads=[[0,3],[2,4],[1,3]]
- 输出
- 20
最优解:一步一步想明白
- 3记牢这条主线:一座城市的贡献是它的值乘以它的度数。想让总贡献最大,就让度数大的城市拿大值。所以先数度数,再从小到大排,让排在越后面度数越大的城市,拿到越大的值。下面先在图上把度数数出来。
- 4先数每座城市连了几条路(度数)舞台上是 5 座城市,编号 0 到 4,城市之间的连线就是双向道路。第一步和值多大没关系,先把每座城市的度数数出来,也就是它连了几条路。开始时每座城市旁边的度数都标着 0,接下来一条一条路地扫,给这条路的两个端点各加 1。
- 5这条路连着城 0 和城 1,准备给两头各加 1看第 1 条路 [0, 1]。高亮的就是眼下这条路,它连着城 0 和城 1。数度数很机械,一条路给它的两个端点各记一笔,先不管这两座城市将来分到多大的值。
- 6城 0:1,城 1:1两头各加 1:城 0 的度数变成 1,城 1 的度数变成 1。度数谁高谁低先别急着下结论,扫到一半的中间态容易看走眼,等 6 条路全数完再一起比,才知道谁是最忙的枢纽。
- 7这条路连着城 1 和城 2,准备给两头各加 1看第 2 条路 [1, 2]。高亮的就是眼下这条路,它连着城 1 和城 2。数度数很机械,一条路给它的两个端点各记一笔,先不管这两座城市将来分到多大的值。
- 8城 1:2,城 2:1两头各加 1:城 1 的度数变成 2,城 2 的度数变成 1。度数谁高谁低先别急着下结论,扫到一半的中间态容易看走眼,等 6 条路全数完再一起比,才知道谁是最忙的枢纽。
- 9这条路连着城 2 和城 3,准备给两头各加 1看第 3 条路 [2, 3]。高亮的就是眼下这条路,它连着城 2 和城 3。数度数很机械,一条路给它的两个端点各记一笔,先不管这两座城市将来分到多大的值。
- 10城 2:2,城 3:1两头各加 1:城 2 的度数变成 2,城 3 的度数变成 1。度数谁高谁低先别急着下结论,扫到一半的中间态容易看走眼,等 6 条路全数完再一起比,才知道谁是最忙的枢纽。
- 11这条路连着城 0 和城 2,准备给两头各加 1看第 4 条路 [0, 2]。高亮的就是眼下这条路,它连着城 0 和城 2。数度数很机械,一条路给它的两个端点各记一笔,先不管这两座城市将来分到多大的值。
- 12城 0:2,城 2:3两头各加 1:城 0 的度数变成 2,城 2 的度数变成 3。度数谁高谁低先别急着下结论,扫到一半的中间态容易看走眼,等 6 条路全数完再一起比,才知道谁是最忙的枢纽。
- 13这条路连着城 1 和城 3,准备给两头各加 1看第 5 条路 [1, 3]。高亮的就是眼下这条路,它连着城 1 和城 3。数度数很机械,一条路给它的两个端点各记一笔,先不管这两座城市将来分到多大的值。
- 14城 1:3,城 3:2两头各加 1:城 1 的度数变成 3,城 3 的度数变成 2。度数谁高谁低先别急着下结论,扫到一半的中间态容易看走眼,等 6 条路全数完再一起比,才知道谁是最忙的枢纽。
- 15这条路连着城 2 和城 4,准备给两头各加 1看第 6 条路 [2, 4]。高亮的就是眼下这条路,它连着城 2 和城 4。数度数很机械,一条路给它的两个端点各记一笔,先不管这两座城市将来分到多大的值。
- 16城 2:4,城 4:1两头各加 1:城 2 的度数变成 4,城 4 的度数变成 1。度数谁高谁低先别急着下结论,扫到一半的中间态容易看走眼,等 6 条路全数完再一起比,才知道谁是最忙的枢纽。
- 17城 2 度数 4 最大,城 4 度数 1 最小6 条路全数完了。按城市编号排,度数是 [2, 3, 4, 2, 1]:城 2 连了 4 条路最多,城 1 连 3 条,城 0 和城 3 各连 2 条,城 4 只连 1 条最少。度数一出来,谁该拿大值就清楚了:城 2 该拿最大的 5,城 4 拿最小的 1。下面把这些度数排个序,让分配一目了然。
- 18还没排序,顺序是城 0 到城 4 的度数把刚才每座城市的度数抄成一排,从左到右是城 0 到城 4,值分别是 2、3、4、2、1。现在它是乱的,不方便配值。下一步把它从小到大排好,这样越靠右度数越大,正好对应越大的值。
- 19从小到大:最小度数在最左,最大在最右排好序,现在是 [1, 2, 2, 3, 4]。最左边是最小的度数 1,最右边是最大的度数 4。接下来从左往右走,第 1 个位置配值 1,第 2 个配值 2,一直到第 5 个配值 5。位置越靠右度数越大,拿到的值也越大,这就把大值稳稳配给了大度数。
- 20贡献 = 值 1 × 度数 1 = 1走到第 1 个位置,这里度数是 1,按规矩配上值 1。这座城市的贡献就是值乘度数,1 × 1 = 1。把它加进累计和,累计从 0 变成 1。继续往右,度数更大的位置会配更大的值。
- 21贡献 = 值 2 × 度数 2 = 4走到第 2 个位置,这里度数是 2,按规矩配上值 2。这座城市的贡献就是值乘度数,2 × 2 = 4。把它加进累计和,累计从 1 变成 5。继续往右,度数更大的位置会配更大的值。
- 22贡献 = 值 3 × 度数 2 = 6走到第 3 个位置,这里度数是 2,按规矩配上值 3。这座城市的贡献就是值乘度数,3 × 2 = 6。把它加进累计和,累计从 5 变成 11。继续往右,度数更大的位置会配更大的值。
- 23贡献 = 值 4 × 度数 3 = 12走到第 4 个位置,这里度数是 3,按规矩配上值 4。这座城市的贡献就是值乘度数,4 × 3 = 12。把它加进累计和,累计从 11 变成 23。继续往右,度数更大的位置会配更大的值。
- 24贡献 = 值 5 × 度数 4 = 20走到第 5 个位置,这里度数是 4,按规矩配上值 5。这座城市的贡献就是值乘度数,5 × 4 = 20。把它加进累计和,累计从 23 变成 43。这是最大的度数配上了最大的值,贡献也最大。
- 25各位贡献相加 = 43五个位置全配完了。把每位的贡献 1 + 4 + 6 + 12 + 20 加起来,正好是 43。这就是最优分配下所有道路重要性之和的最大值。回头看,大值都落在了度数大的城市上,一分值也没浪费在冷清的城市上,这就是排序不等式给的最优安排。
⚠️ 容易写错的地方
✗ 错:用 int 存累计和
✓ 对:用 long 或 long long,且乘法先转长整型
n 和度数都可到 5 万量级,值乘度数再累加会超过 32 位整数范围,int 会溢出得到错误答案;Python 大整数不受影响
✗ 错:把大的值配给度数小的城市
✓ 对:大值配大度数,升序度数第 i 位配值 i
按排序不等式,同样一堆值和一堆度数,把两边都从大到小对齐相乘,总和才最大;配反了总和会变小
✗ 错:真去枚举每种值的排列找最优
✓ 对:只需数度数、排序、按位配值
排列有 n 的阶乘种,根本枚举不完;贡献 = 值 × 度数 这一拆解,直接把问题变成排序,一步到位
✗ 错:以为要真的构造出分配数组再算
✓ 对:只需要排好序的度数即可求和
答案只依赖每座城市的度数和它配到的值,把升序度数逐位乘上 1 到 n 求和就够了,不必真的记录哪座城市分到几
完整代码(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 maximumImportance(self, n: int, roads: List[List[int]]) -> int:
deg = [0] * n
for a, b in roads:
deg[a] += 1
deg[b] += 1
deg.sort()
return sum(i * v for i, v in enumerate(deg, 1))C++
#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:
long long maximumImportance(int n, vector<vector<int>>& roads) {
vector<int> deg(n);
for (auto& r : roads) {
++deg[r[0]];
++deg[r[1]];
}
sort(deg.begin(), deg.end());
long long ans = 0;
for (int i = 0; i < n; ++i) {
ans += (i + 1LL) * deg[i];
}
return ans;
}
};Java
import java.util.*;
class Solution {
public long maximumImportance(int n, int[][] roads) {
int[] deg = new int[n];
for (int[] r : roads) {
++deg[r[0]];
++deg[r[1]];
}
Arrays.sort(deg);
long ans = 0;
for (int i = 0; i < n; ++i) {
ans += (long) (i + 1) * deg[i];
}
return ans;
}
}复杂度
时间
O(m + n log n)
m 是路的条数,数度数扫一遍 roads 是 O(m);再对 n 个度数排序是 O(n log n);最后遍历累加是 O(n)。主导项是排序的 O(n log n),整体就是 O(m + n log n)
空间
O(n)
按峰值算,主要是那个长度为 n 的度数数组,O(n)。排序本身的额外开销:C plus plus 和 Java 通常是 O(log n) 递归栈,Python 的排序最坏 O(n),都不超过度数数组的量级,所以整体空间 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 道路的最大总重要性 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题的核心洞察是什么?+
关键是把总和换个角度拆。一条路的重要性是两端城市值之和,如果直接盯着路看,很难下手。换成盯着城市看:某座城市的值,会被它连的每一条路各加进总和一次,所以它对答案的总贡献等于它的值乘以它的度数。于是整个答案就是所有城市的值乘度数之和。剩下的就是一个分配问题:把 1 到 n 这组值分给各城市,让 Σ 值×度数 最大。
为什么大值配大度数就是最优,能证明吗?+
这是排序不等式的直接结论。给定两组数,一组是值 1 到 n,一组是各城市的度数,把它们两两配对相乘再求和,当两组都按同样的大小顺序对齐时和最大,按相反顺序对齐时和最小。直觉上,度数大的城市每加一次值都被放大更多次,当然要把大的值给它。实现上不需要真配对,只要把度数升序排好,第 i 小的度数乘以值 i,累加即可,等价于大值配大度数。
复杂度是多少,有什么实现坑?+
数度数扫一遍路是 O(m),排序 n 个度数是 O(n log n),累加是 O(n),整体 O(m + n log n),空间 O(n)。最大的实现坑是溢出:n 和度数都能到 5 万量级,值乘度数再累加会超出 32 位整数,C plus plus 和 Java 必须用 long long 或 long 存和,而且乘法要先转长整型再乘;Python 的整数是大整数,不用担心。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 道路的最大总重要性 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。