重新规划路线 图解题解
这道题到底在问什么
- 输入
- n=6, connections=[[0,1],[1,3],[2,3],[4,0],[4,5]]
- 输出
- 3(反转 0→1、1→3、4→5 这三条背离 0 的边)
- 输入
- n=3, connections=[[1,0],[2,0]]
- 输出
- 0(两条都已朝向 0)
最优解:一步一步想明白
- 3记住「从 0 往外走,遇到背离 0 的原方向边(cost=1)就计一次反转」,下面逐帧套它。
- 4先看这张有向图:箭头就是道路的单行方向。0 号城市(绿)是大家的目的地。现在从 0 出发,沿树边往外走,逐条检查每条边的箭头是不是朝着 0。
- 5从根 0 开始,看它直接相连的城市。
- 6检查通往城市 1 的这条边(高亮)。看它的箭头:从 0 指向 1。
- 7箭头从 0 指向 1,是背离 0 的方向,城市 1(及它后面的城市)没法靠这条边回到 0,必须反转。反转计数 +1 = 1。
- 8检查通往城市 4 的这条边(高亮)。看它的箭头:从 4 指向 0。
- 9箭头从 4 指向 0,正好朝向 0,城市 4 顺着它就能往 0 走,无需反转。计数仍是 1。
- 10轮到城市 1:它已经能回到 0,现在看从它再往外的边。
- 11检查通往城市 3 的这条边(高亮)。看它的箭头:从 1 指向 3。
- 12箭头从 1 指向 3,是背离 0 的方向,城市 3(及它后面的城市)没法靠这条边回到 0,必须反转。反转计数 +1 = 2。
- 13轮到城市 4:它已经能回到 0,现在看从它再往外的边。
- 14检查通往城市 5 的这条边(高亮)。看它的箭头:从 4 指向 5。
- 15箭头从 4 指向 5,是背离 0 的方向,城市 5(及它后面的城市)没法靠这条边回到 0,必须反转。反转计数 +1 = 3。
- 16轮到城市 3:它已经能回到 0,现在看从它再往外的边。
- 17检查通往城市 2 的这条边(高亮)。看它的箭头:从 2 指向 3。
- 18箭头从 2 指向 3,正好朝向 0,城市 2 顺着它就能往 0 走,无需反转。计数仍是 3。
- 19城市 5 已经接进来了,它没有更外层的新邻居,这条分支到头。
- 20城市 2 已经接进来了,它没有更外层的新邻居,这条分支到头。
- 21整棵树遍历完。一路上遇到 3 条「背离 0」的原方向边,它们就是必须反转的。下面把这 3 条逐一点亮。
- 22反转边 0→1:让它改朝 0 的方向,这样城市 1 这一支就能回到 0 了。
- 23反转边 1→3:让它改朝 0 的方向,这样城市 3 这一支就能回到 0 了。
- 24反转边 4→5:让它改朝 0 的方向,这样城市 5 这一支就能回到 0 了。
- 25三条边全部反转后,从任何城市出发都能开回 0 号城市。最少反转次数 = 3。回头看:整道题就是「从 0 往外走一遍,数有几条原方向背离 0 的边」。
⚠️ 容易写错的地方
✗ 错:只按原方向建单向图
✓ 对:建双向图,原方向 cost=1、反方向 cost=0
从 0 出发要能沿树边走遍全图,只存单向会卡住走不动;用 cost 区分原/反方向
✗ 错:遍历时不记 parent,走回头路
✓ 对:带上来时的父节点,跳过它
双向图里每条边都能往回走,不挡住父亲会无限循环、还会重复计数
✗ 错:以为要真去修改边的方向
✓ 对:只需统计要反转的条数,不必真改图
题目问的是「最少反转次数」这个数字,数出来即可
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def minReorder(self, n: int, connections: List[List[int]]) -> int:
g = [[] for _ in range(n)]
for a, b in connections:
g[a].append((b, 1))
g[b].append((a, 0))
ans = 0
stack = [(0, -1)]
while stack:
u, parent = stack.pop()
for v, cost in g[u]:
if v != parent:
ans += cost
stack.append((v, u))
return ansC++
#include <vector>
using namespace std;
class Solution {
public:
int minReorder(int n, vector<vector<int>>& connections) {
vector<vector<pair<int,int>>> g(n);
for (auto &e : connections) {
g[e[0]].push_back({e[1], 1});
g[e[1]].push_back({e[0], 0});
}
int ans = 0;
vector<pair<int,int>> st{{0, -1}};
while (!st.empty()) {
auto [u, p] = st.back(); st.pop_back();
for (auto [v, cost] : g[u]) if (v != p) {
ans += cost;
st.push_back({v, u});
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int minReorder(int n, int[][] connections) {
List<int[]>[] g = new ArrayList[n];
for (int i = 0; i < n; i++) g[i] = new ArrayList<>();
for (int[] e : connections) {
g[e[0]].add(new int[]{e[1], 1});
g[e[1]].add(new int[]{e[0], 0});
}
int ans = 0;
Deque<int[]> st = new ArrayDeque<>();
st.push(new int[]{0, -1});
while (!st.isEmpty()) {
int[] cur = st.pop();
for (int[] nxt : g[cur[0]]) if (nxt[0] != cur[1]) {
ans += nxt[1];
st.push(new int[]{nxt[0], cur[0]});
}
}
return ans;
}
}复杂度
时间
O(n)
树有 n−1 条边,每条边在邻接表里出现两次,遍历各访问一次,共 O(n)
空间
O(n)
邻接表存 2(n−1) 条有向记录,加 seen / 栈,都是 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 重新规划路线 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么可以保证这棵图是树、路径唯一?+
题目给定 n 个城市、恰好 n−1 条边,且要求「能让所有城市到达 0」隐含图是连通的。n 个点、n−1 条边且连通,正是树的定义:无环、任意两点间路径唯一。正因为路径唯一,每条边「该朝哪个方向」是确定的:朝向通往 0 的那一侧。
能不能不建 cost,边遍历边判断方向?+
可以。遍历到边 (u,v) 时,直接查这条边在原始 connections 里是不是 u→v:是就说明它背离 0(因为我们正从 u 往外走向 v),要反转。预先存 cost 只是把这个判断提前算好、让遍历更干净,本质一样。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 重新规划路线 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。