2025-12-07:使叶子路径成本相等的最小增量。用go语言,给出一个整数 n 和一棵以节点 0 为根的树(节点编号 0 到 n−1)。树用长度为 n−1 的边数组 edges 表示,其中 edges[i] = [u_i, v_i] 表示节点 u_i 与 v_i 相连。每个节点 i 关联一个代价 cost[i],一条路径的“权重”定义为路径上所有节点代价的和。
允许你对任意若干节点各自增加一个不小于 0 的数(不同节点的增量可以不同);如果某节点增加的值为 0,则视为未改变。目标是通过这样的增量使得从根 0 到所有叶子节点的路径权重都相等。请返回需要被增大的节点的最小数量。
2 <= n <= 100000。
edges.length == n - 1。
edges[i] == [ui, vi]。
0 <= ui, vi < n。
cost.length == n。
1 <= cost[i] <= 1000000000。
输入保证 edges 表示一棵合法的树。
输入: n = 3, edges = [[0,1],[0,2]], cost = [2,1,3]。
输出: 1。
解释:
树中有两条从根到叶子的路径:
路径 0 → 1 的得分为 2 + 1 = 3。
路径 0 → 2 的得分为 2 + 3 = 5。
为了使所有路径的得分都等于 5,可以将节点 1 的成本增加 2。
仅需增加一个节点的成本,因此输出为 1。
题目来自力扣3593。
大体步骤
1. 构建树的邻接表
代码首先根据输入的边数组 edges 构建了一个邻接表 g,用于表示树的结构。这是一个无向图,因此每条边都会在邻接表中为两个节点相互添加连接。特别地,代码为根节点0额外添加了一个指向-1的边,这是为了避免在后续处理中将根节点误判为叶子节点。
2. 深度优先搜索(DFS)与贪心调整
核心逻辑在 dfs 函数中,它采用后序遍历(先处理子节点,再处理当前节点)的方式自底向上地计算和调整路径成本。
- 叶子节点处理:当一个节点是叶子节点时(在邻接表中除了父节点只有一个连接),它到根节点的路径成本就是其自身的
cost。DFS函数会直接返回这个成本值。 - 非叶子节点处理:对于非叶子节点,DFS会递归地处理其所有子节点,并收集每个子节点传递上来的、从该子节点到其下属叶子节点的最大路径成本(
mx)。 - 寻找最大成本与计数:在处理当前节点的所有子节点时,算法会追踪遇到的最大路径成本
maxS,并记录有多少个子节点能提供这个最大成本(cnt)。这个过程确保了算法能识别出当前节点子树下“最长”的路径。 - 贪心决策与计数:关键的贪心策略在于,为了让所有从当前节点出发到叶子节点的路径成本相等,只需要让那些路径成本较小的子节点向最大的路径成本看齐。具体实现上,算法不会去实际修改节点的
cost值,而是通过计算需要增加成本的节点数量来解决问题。公式ans += len(g[x]) - 1 - cnt的含义是:len(g[x]) - 1:当前节点x拥有的有效子节点数量(因为邻接表中包含指向父节点的边,需要减去)。cnt:已经拥有最大路径成本的子节点数量。- 两者之差,就是那些路径成本不是最大的子节点数量。对于这些子节点所在的路径,我们需要通过增加其中某个节点的成本来提升整条路径的成本。这个“某个节点”的调整可以等效地上推到当前节点
x的子节点上。因此,这个差值就是在当前节点层面,需要执行成本增加的节点数量。
- 返回路径成本:最后,DFS函数返回从当前节点
x到其叶子节点的最大路径成本,即maxS + cost[x]。这个值将提供给x的父节点用于计算。
3. 算法启动
从根节点0开始调用DFS,初始的父节点设为-1。DFS遍历完整棵树后,累加器 ans 中存储的值就是所需的最小增量节点数量,将其返回。
复杂度分析
时间复杂度:O(n)
- 建图:需要处理
n-1条边,时间复杂度为 O(n)。 - DFS遍历:每个节点恰好被访问一次,在每次访问时,处理其所有子节点。虽然每个节点可能有多个子节点,但整棵树的边总数是
n-1,因此所有节点的子节点处理次数总和是 O(n)。DFS的整体时间复杂度为 O(n)。 - 综上,算法的总时间复杂度为 O(n)。
空间复杂度:O(n)
- 邻接表存储:需要存储
n个节点的邻接关系,空间复杂度为 O(n)。 - 递归调用栈:在最坏情况下(树退化为一条链),递归深度为
n,空间复杂度为 O(n)。 - 综上,算法的总空间复杂度为 O(n)。
Go完整代码如下:
package main
import (
"fmt"
)
func minIncrease(n int, edges [][]int, cost []int) (ans int) {
g := make([][]int, n)
for _, e := range edges {
x, y := e[0], e[1]
g[x] = append(g[x], y)
g[y] = append(g[y], x)
}
g[0] = append(g[0], -1)
var dfs func(int, int) int
dfs = func(x, fa int) (maxS int) {
cnt := 0
for _, y := range g[x] {
if y == fa {
continue
}
mx := dfs(y, x)
if mx > maxS {
maxS = mx
cnt = 1
} else if mx == maxS {
cnt++
}
}
ans += len(g[x]) - 1 - cnt
return maxS + cost[x]
}
dfs(0, -1)
return
}
func main() {
n := 3
edges := [][]int{{0, 1}, {0, 2}}
cost := []int{2, 1, 3}
result := minIncrease(n, edges, cost)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
from typing import List
def minIncrease(n: int, edges: List[List[int]], cost: List[int]) -> int:
g = [[] for _ in range(n)]
for x, y in edges:
g[x].append(y)
g[y].append(x)
g[0].append(-1) # 添加虚拟父节点,方便处理根节点
ans = 0
def dfs(x: int, fa: int) -> int:
nonlocal ans
maxS = 0
cnt = 0
for y in g[x]:
if y == fa:
continue
mx = dfs(y, x)
if mx > maxS:
maxS = mx
cnt = 1
elif mx == maxS:
cnt += 1
ans += len(g[x]) - 1 - cnt
return maxS + cost[x]
dfs(0, -1)
return ans
if __name__ == "__main__":
n = 3
edges = [[0, 1], [0, 2]]
cost = [2, 1, 3]
result = minIncrease(n, edges, cost)
print(result)
C++完整代码如下:
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
int minIncrease(int n, vector<vector<int>>& edges, vector<int>& cost) {
vector<vector<int>> g(n);
// 构建邻接表
for (auto& e : edges) {
int x = e[0], y = e[1];
g[x].push_back(y);
g[y].push_back(x);
}
g[0].push_back(-1); // 添加虚拟父节点,方便处理根节点
int ans = 0;
// 使用 function 包装递归函数
function<int(int, int)> dfs = [&](int x, int fa) -> int {
int maxS = 0;
int cnt = 0;
for (int y : g[x]) {
if (y == fa) continue;
int mx = dfs(y, x);
if (mx > maxS) {
maxS = mx;
cnt = 1;
} else if (mx == maxS) {
cnt++;
}
}
ans += (int)g[x].size() - 1 - cnt;
return maxS + cost[x];
};
dfs(0, -1);
return ans;
}
int main() {
int n = 3;
vector<vector<int>> edges = {{0, 1}, {0, 2}};
vector<int> cost = {2, 1, 3};
int result = minIncrease(n, edges, cost);
cout << result << endl;
return 0;
}