LeetCode 题解工作台
最小化旅行的价格总和
现有一棵无向、无根的树,树中有 n 个节点,按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [a i , b i ] 表示树中节点 a i 和 b i 之间存在一条边。 每个节点都关联一个价格。给你一个整数数组 p…
5
题型
5
代码语言
3
相关题
当前训练重点
困难 · 图·DFS·traversal
答案摘要
我们可以统计每一次旅行经过的节点,记录在数组 中,其中 表示所有旅行中经过节点 的次数。我们设计一个函数 $dfs(i, fa, k)$,表示从节点 开始搜索,最终到达节点 ,过程中记录所有经过的节点。其中 表示节点 的父节点。 接下来,我们再设计一个函数 $dfs2(i, fa)$,表示从节点 开始搜索,返回将节点 的价格不减半或者减半后的最小价格总和。其中 表示节点 的父节…
Interview AiBoxInterview AiBox 实时 AI 助手,陪你讲清 图·DFS·traversal 题型思路
题目描述
现有一棵无向、无根的树,树中有 n 个节点,按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。
每个节点都关联一个价格。给你一个整数数组 price ,其中 price[i] 是第 i 个节点的价格。
给定路径的 价格总和 是该路径上所有节点的价格之和。
另给你一个二维整数数组 trips ,其中 trips[i] = [starti, endi] 表示您从节点 starti 开始第 i 次旅行,并通过任何你喜欢的路径前往节点 endi 。
在执行第一次旅行之前,你可以选择一些 非相邻节点 并将价格减半。
返回执行所有旅行的最小价格总和。
示例 1:
输入:n = 4, edges = [[0,1],[1,2],[1,3]], price = [2,2,10,6], trips = [[0,3],[2,1],[2,3]] 输出:23 解释: 上图表示将节点 2 视为根之后的树结构。第一个图表示初始树,第二个图表示选择节点 0 、2 和 3 并使其价格减半后的树。 第 1 次旅行,选择路径 [0,1,3] 。路径的价格总和为 1 + 2 + 3 = 6 。 第 2 次旅行,选择路径 [2,1] 。路径的价格总和为 2 + 5 = 7 。 第 3 次旅行,选择路径 [2,1,3] 。路径的价格总和为 5 + 2 + 3 = 10 。 所有旅行的价格总和为 6 + 7 + 10 = 23 。可以证明,23 是可以实现的最小答案。
示例 2:
输入:n = 2, edges = [[0,1]], price = [2,2], trips = [[0,0]] 输出:1 解释: 上图表示将节点 0 视为根之后的树结构。第一个图表示初始树,第二个图表示选择节点 0 并使其价格减半后的树。 第 1 次旅行,选择路径 [0] 。路径的价格总和为 1 。 所有旅行的价格总和为 1 。可以证明,1 是可以实现的最小答案。
提示:
1 <= n <= 50edges.length == n - 10 <= ai, bi <= n - 1edges表示一棵有效的树price.length == nprice[i]是一个偶数1 <= price[i] <= 10001 <= trips.length <= 1000 <= starti, endi <= n - 1
解题思路
方法一:树形 DP
我们可以统计每一次旅行经过的节点,记录在数组 中,其中 表示所有旅行中经过节点 的次数。我们设计一个函数 ,表示从节点 开始搜索,最终到达节点 ,过程中记录所有经过的节点。其中 表示节点 的父节点。
接下来,我们再设计一个函数 ,表示从节点 开始搜索,返回将节点 的价格不减半或者减半后的最小价格总和。其中 表示节点 的父节点。
我们从节点 开始,对于每一个节点 ,我们可以选择不将其价格减半,也可以选择将其价格减半,分别记为 , 。
对于节点 的所有邻接节点 ,我们依然可以选择不将其价格减半,也可以选择将其价格减半,那么 。如果节点 价格不减半,那么节点 价格可以不减半,也可以减半,因此 ;如果节点 价格减半,那么节点 价格必须不减半,因此 。
最后,函数 的返回值为 。
在主函数中,我们调用函数 ,返回值为 ,那么最终的答案即为 。
时间复杂度 ,空间复杂度 。其中 和 分别为旅行次数以及节点数。
class Solution:
def minimumTotalPrice(
self, n: int, edges: List[List[int]], price: List[int], trips: List[List[int]]
) -> int:
def dfs(i: int, fa: int, k: int) -> bool:
cnt[i] += 1
if i == k:
return True
ok = any(j != fa and dfs(j, i, k) for j in g[i])
if not ok:
cnt[i] -= 1
return ok
def dfs2(i: int, fa: int) -> (int, int):
a = cnt[i] * price[i]
b = a // 2
for j in g[i]:
if j != fa:
x, y = dfs2(j, i)
a += min(x, y)
b += x
return a, b
g = [[] for _ in range(n)]
for a, b in edges:
g[a].append(b)
g[b].append(a)
cnt = Counter()
for start, end in trips:
dfs(start, -1, end)
return min(dfs2(0, -1))
复杂度分析
| 指标 | 值 |
|---|---|
| 时间 | complexity is O(n * trips) for computing DFS paths plus O(n) for the DP traversal. Space complexity is O(n) for frequency array and DP states, keeping memory usage linear relative to the number of nodes. |
| 空间 | Depends on the final approach |
面试官常问的追问
外企场景- question_mark
Check if the candidate identifies DFS as the tool for counting node frequencies.
- question_mark
Listen for correct formulation of two DP states per node to consider halving or not.
- question_mark
Observe if they optimize repeated trip paths using memoization or frequency accumulation.
常见陷阱
外企场景- error
Forgetting to count multiple visits of a node across different trips, underestimating its contribution.
- error
Incorrectly combining DP states, leading to non-optimal halving choices.
- error
Attempting brute-force path enumeration without DFS, resulting in timeouts even for small n.
进阶变体
外企场景- arrow_right_alt
Instead of halving node prices, consider reducing each node price by a fixed amount and compute minimum total cost.
- arrow_right_alt
Allow edges to have weights and optimize trips cost including edge weights along with node prices.
- arrow_right_alt
Limit the number of nodes that can be halved and find the minimal achievable total trip cost.