题目描述
这是 LeetCode 上的 「2477. 到达首都的最少油耗」 ,难度为 「中等」。
Tag : 「DFS」
给你一棵 n
个节点的树(一个无向、连通、无环图),每个节点表示一个城市,编号从 0
到 n - 1
,且恰好有 n - 1
条路。
0
是首都。给你一个二维整数数组 roads
,其中
,表示城市
和
之间有一条 双向路 。
每个城市里有一个代表,他们都要去首都参加一个会议。
每座城市里有一辆车。给你一个整数 seats
表示每辆车里面座位的数目。
城市里的代表可以选择乘坐所在城市的车,或者乘坐其他城市的车。相邻城市之间一辆车的油耗是一升汽油。
请你返回到达首都最少需要多少升汽油。
示例 1:
输入:roads = [[0,1],[0,2],[0,3]], seats = 5
输出:3
解释:
- 代表 1 直接到达首都,消耗 1 升汽油。
- 代表 2 直接到达首都,消耗 1 升汽油。
- 代表 3 直接到达首都,消耗 1 升汽油。
最少消耗 3 升汽油。
示例 2:
输入:roads = [[3,1],[3,2],[1,0],[0,4],[0,5],[4,6]], seats = 2
输出:7
解释:
- 代表 2 到达城市 3 ,消耗 1 升汽油。
- 代表 2 和代表 3 一起到达城市 1 ,消耗 1 升汽油。
- 代表 2 和代表 3 一起到达首都,消耗 1 升汽油。
- 代表 1 直接到达首都,消耗 1 升汽油。
- 代表 5 直接到达首都,消耗 1 升汽油。
- 代表 6 到达城市 4 ,消耗 1 升汽油。
- 代表 4 和代表 6 一起到达首都,消耗 1 升汽油。
最少消耗 7 升汽油。
示例 3:
输入:roads = [], seats = 1
输出:0
解释:没有代表需要从别的城市到达首都。
提示:
-
-
-
-
-
-
roads
表示一棵合法的树。 -
DFS
将双向图看作是以节点 0
为根的有向树,从每个节点出发往 0
前行,可看作是自底向上的移动过程。
当 seats = 1
时,每个节点前往 0
的过程相互独立,总油耗为每节点到 0
的最短距离之和。
当 seats
不为 1
时,考虑组成顺风车,此时总的油耗不该超过 seats = 1
的情况。
不难发现,「只有「深度大的节点,在前往 0
过程中,搭乘深度小顺风车」可减少油耗」(例如在上图节点 3
在经过节点 1
时搭乘顺风车,可与节点 1
合计使用一份油耗前往到 0
),否则如果是深度小的节点先往深度大的节点走,再一同前往 0
,会额外多经过某些边,产生不必要的油耗。
考虑组成顺风车时,总油耗该如何计算。
基于上述分析,无论 seats
是否为
(是否组成顺风车),每个节点前往 0
的路径总是不变,即经过的边固定不变,必然是自底向上。
因此我们可统计每条边会被多少个节点经过,通过 DFS
统计「以每个节点为根时,子树的节点数量」即是经过该节点往上的边。
Java 代码:
class Solution {
int N = 100010, M = 2 * N, idx = 0;
int[] he = new int[N], e = new int[M], ne = new int[M];
void add(int a, int b) {
e[idx] = b;
ne[idx] = he[a];
he[a] = idx++;
}
long ans = 0;
public long minimumFuelCost(int[][] roads, int seats) {
int n = roads.length + 1;
Arrays.fill(he, -1);
for (int[] r : roads) {
int a = r[0], b = r[1];
add(a, b); add(b, a);
}
dfs(0, -1, seats);
return ans;
}
int dfs(int u, int fa, int t) {
int cnt = 1;
for (int i = he[u]; i != -1; i = ne[i]) {
int j = e[i];
if (j == fa) continue;
cnt += dfs(j, u, t);
}
if (u != 0) ans += Math.ceil(cnt * 1.0 / t);
return cnt;
}
}
C++ 代码:
class Solution {
public:
int N = 100010, M = 2 * N, idx = 0;
int he[100010], e[200020], ne[200020];
long long ans = 0;
void add(int a, int b) {
e[idx] = b;
ne[idx] = he[a];
he[a] = idx++;
}
long long minimumFuelCost(vector<vector<int>>& roads, int seats) {
int n = roads.size() + 1;
memset(he, -1, sizeof(he));
for (auto& r : roads) {
int a = r[0], b = r[1];
add(a, b); add(b, a);
}
dfs(0, -1, seats);
return ans;
}
int dfs(int u, int fa, int t) {
int cnt = 1;
for (int i = he[u]; i != -1; i = ne[i]) {
int j = e[i];
if (j == fa) continue;
cnt += dfs(j, u, t);
}
if (u != 0) ans += ceil(cnt * 1.0 / t);
return cnt;
}
};
Python 代码:
class Solution:
def minimumFuelCost(self, roads: List[List[int]], seats: int) -> int:
n, m, ans = len(roads) + 1, len(roads) * 2 + 1, 0
he, e, ne, idx = [-1] * n, [0] * m, [0] * m, 0
def add(a, b):
nonlocal idx
e[idx] = b
ne[idx] = he[a]
he[a] = idx
idx += 1
def dfs(u, fa, t):
nonlocal ans
cnt = 1
i = he[u]
while i != -1:
j, i = e[i], ne[i]
if j == fa: continue
cnt += dfs(j, u, t)
if u != 0:
ans += math.ceil(cnt * 1.0 / t)
return cnt
for a, b in roads:
add(a, b)
add(b, a)
dfs(0, -1, seats)
return ans
TypeScript 代码:
function minimumFuelCost(roads: number[][], seats: number): number {
const n = roads.length + 1, m = n * 2;
const he = Array(n).fill(-1), e = Array(m).fill(0), ne = Array(m).fill(0);
let ans = 0, idx = 0;
const add = function(a: number, b: number) {
e[idx] = b;
ne[idx] = he[a];
he[a] = idx++;
}
const dfs = function(u: number, fa: number, t: number): number {
let cnt = 1;
for (let i = he[u]; i != -1; i = ne[i]) {
const j = e[i];
if (j == fa) continue;
cnt += dfs(j, u, t);
}
if (u != 0) ans += Math.ceil(cnt * 1.0 / t);
return cnt;
}
for (const [a, b] of roads) {
add(a, b); add(b, a);
}
dfs(0, -1, seats);
return ans;
};
-
时间复杂度:建图复杂度为 ;递归统计每个子树节点数量并计算油耗复杂度 ;整体复杂度为 -
空间复杂度:
最后
这是我们「刷穿 LeetCode」系列文章的第 No.2477
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉