1. 问题背景与理解
今天我们来拆解一道经典的图论面试题——LeetCode 1615. 最大网络秩(Maximal Network Rank)。这道题在2023年的面试中频繁出现,特别是在考察候选人基础图论能力时。题目看似简单,但想要写出高效解法需要深入理解图的结构特性。
题目描述:给定n个城市(编号从0到n-1)和连接这些城市的道路列表roads,其中roads[i] = [a_i, b_i]表示城市a_i和b_i之间有一条双向道路。我们需要找出所有城市对中"网络秩"的最大值。网络秩的定义是:如果两个城市直接相连,则它们的网络秩为两者度数之和减1;否则就是两者度数之和。
举个例子,假设城市A有3条连接道路,城市B有2条连接道路:
- 如果A和B直接相连,则这对城市的网络秩=3+2-1=4
- 如果A和B不相连,则网络秩=3+2=5
这个定义看起来有点反直觉——为什么相连的城市对反而要减1?其实这是因为直接连接的那条边被两个城市各统计了一次,需要去重。
2. 暴力解法与优化思路
2.1 最直观的暴力解法
对于刚接触这道题的同学,最直接的想法可能是:
- 计算每个城市的度数(邻接表大小)
- 遍历所有城市对(i,j)
- 对于每对城市,检查它们是否直接相连
- 根据连接情况计算网络秩
- 维护最大值
这种解法的时间复杂度是O(n^2),在n=100时(LeetCode的约束条件)完全可行。但面试官通常会追问:"如果n很大,比如n=10^5呢?"
2.2 优化思路分析
仔细观察我们会发现,最大网络秩实际上只与度数最大的几个城市有关。考虑以下性质:
- 最大网络秩的上限是最大度数+次大度数
- 只有当所有最大度数的城市都互相连接时,才需要减去1
因此优化策略是:
- 找出度数最大的城市集合max_nodes
- 找出度数次大的城市集合second_max_nodes
- 如果max_nodes中有至少两个不直接相连的城市,答案就是max_degree + max_degree
- 否则检查max_nodes和second_max_nodes之间是否有不直接相连的对
- 如果都没有,则答案是max_degree + second_max_degree - 1
这种优化可以将时间复杂度降到O(n + m),其中m是道路数量。
3. 代码实现与细节处理
3.1 邻接表表示法
首先我们需要高效地表示图结构并计算度数。邻接表是最合适的选择:
python复制from collections import defaultdict
n = 4
roads = [[0,1],[0,3],[1,2],[1,3]]
# 构建邻接表和度数统计
adj = defaultdict(set)
degree = [0] * n
for a, b in roads:
adj[a].add(b)
adj[b].add(a)
degree[a] += 1
degree[b] += 1
3.2 寻找最大度数节点
接下来我们找出度数最大的节点集合:
python复制max_degree = max(degree)
max_nodes = [i for i in range(n) if degree[i] == max_degree]
3.3 处理不同情况
根据max_nodes的数量,我们有不同处理逻辑:
python复制if len(max_nodes) > 1:
# 检查max_nodes中是否有不相连的对
for i in range(len(max_nodes)):
for j in range(i+1, len(max_nodes)):
if max_nodes[j] not in adj[max_nodes[i]]:
return max_degree * 2
return max_degree * 2 - 1
else:
# 处理只有一个最大度数节点的情况
second_degree = -1
second_nodes = []
for d in degree:
if d < max_degree and d > second_degree:
second_degree = d
second_nodes = [i for i in range(n) if degree[i] == second_degree]
for node in second_nodes:
if node not in adj[max_nodes[0]]:
return max_degree + second_degree
return max_degree + second_degree - 1
4. 边界条件与测试用例
4.1 常见边界情况
这道题有几个容易出错的边界条件:
- 只有两个城市一条路:网络秩应为1
- 所有城市都互不相连:网络秩为0
- 完全图(所有城市两两相连):网络秩为2*(n-1)-1 = 2n-3
4.2 测试用例设计
好的测试用例应该覆盖这些边界情况:
python复制test_cases = [
(2, [[0,1]], 1), # 最小情况
(4, [[0,1],[1,2],[2,3]], 2), # 链式连接
(4, [[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]], 5), # 完全图
(5, [], 0), # 没有道路
(3, [[0,1],[0,2]], 3) # 星型连接
]
5. 复杂度分析与优化证明
5.1 时间复杂度
优化后的算法主要时间消耗在:
- 构建邻接表:O(m)
- 计算度数:O(m)
- 寻找最大/次大度数节点:O(n)
- 检查节点对是否相连:最坏O(k^2),其中k是最大度数节点数量
在实际情况中,k通常很小,因此整体复杂度接近O(n + m),比暴力解法的O(n^2)高效得多。
5.2 正确性证明
为什么这种方法能找到最大网络秩?我们可以用反证法:
假设存在一对城市(i,j)的网络秩比我们找到的更大,那么:
- 要么i或j的度数比max_degree更大(与定义矛盾)
- 要么i和j的度数之和减去连接情况比我们找到的对更大(与我们的搜索过程矛盾)
因此算法一定能找到正确的最大值。
6. 实际面试中的变体问题
在面试中,面试官可能会基于这道题提出一些变体问题,例如:
- 如果道路是单向的(有向图),如何修改算法?
- 如何找出所有达到最大网络秩的城市对?
- 如果要求网络秩恰好等于某个值k的城市对数量,如何解决?
对于第一个变体,我们需要调整网络秩的定义:
- 对于有向边a→b,它只贡献给a的出度
- 网络秩计算变为:出度(u) + 入度(v) - (如果存在u→v则减1)
7. 同类问题拓展
掌握这道题的核心思路后,可以解决许多类似的图论问题:
- 找到图中的枢纽节点(度数最大的节点)
- 计算图的密度(边数与可能边数的比值)
- 寻找图中的密集子图(高网络秩的子图)
特别是第3个变体,在社交网络分析中非常实用,可以用来发现紧密联系的社群。
8. 实际工程中的应用场景
虽然这是一道算法题,但其核心思想在实际工程中有广泛应用:
- 网络规划:确定哪些节点对之间的带宽需求最大
- 社交网络分析:找出最具影响力的用户对
- 交通规划:识别交通流量最大的城市对
例如,在社交网络推荐系统中,我们可能希望推荐那些共同好友很多但尚未连接的用户,这本质上就是在寻找网络秩高的未连接对。
9. 个人解题心得
在多次解决这个问题后,我总结出几点经验:
- 不要被题目描述迷惑,网络秩的定义虽然有点绕,但画图举例就能明白
- 暴力解法在n较小时完全可行,不要过早优化
- 优化时要抓住问题的数学本质——最大网络秩只与最大几个度数节点有关
- 检查节点是否相连时,使用集合比列表查找更高效
- 边界条件一定要单独测试,特别是空图和完全图的情况
一个容易忽略的细节是:当最大度数节点只有一个时,我们需要同时考虑次大度数节点,而不是只看最大度数节点自身。
