Medium

题目描述

给定一个整数 n 和一个以节点 0 为根的无向树,树有 n 个节点,编号从 0 到 n - 1。这用一个长度为 n - 1 的二维数组 edges 表示,其中 edges[i] = [ui, vi] 表示节点 ui 和 vi 之间有一条边。

每个节点 i 都有一个关联的代价 cost[i],表示遍历该节点的成本。

路径的分数定义为路径上所有节点代价的总和。

你的目标是通过增加任意数量节点的代价(增加任意非负数量),使所有从根到叶子的路径分数相等。

返回必须增加代价的最少节点数量,以使所有根到叶子路径分数相等。

示例 1:

输入: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。

示例 2:

输入:n = 3, edges = [[0,1],[1,2]], cost = [5,1,4]
输出:0
解释:
只有一条根到叶子路径:
- 路径 0 → 1 → 2 的分数为 5 + 1 + 4 = 10

由于只有一条根到叶子路径,所有路径代价都相等,输出是 0。

示例 3:

输入:n = 5, edges = [[0,4],[0,1],[1,2],[1,3]], cost = [3,4,1,1,7]
输出:1
解释:
有三条根到叶子路径:
- 路径 0 → 4 的分数为 3 + 7 = 10
- 路径 0 → 1 → 2 的分数为 3 + 4 + 1 = 8
- 路径 0 → 1 → 3 的分数为 3 + 4 + 1 = 8

为了使所有根到叶子路径分数等于 10,需要将节点 1 的代价增加 2。因此,输出是 1。

约束:

  • 2 <= n <= 10^5
  • edges.length == n - 1
  • edges[i] == [ui, vi]
  • 0 <= ui, vi < n
  • cost.length == n
  • 1 <= cost[i] <= 10^9
  • 输入保证 edges 表示一个有效的树

解题思路

解题思路

这是一个关于树形动态规划的问题。核心思想是要让所有根到叶子的路径权值相等,我们需要找到最大的路径权值作为目标值,然后计算最少需要修改多少个节点。

解题步骤:

  1. 构建树结构:根据edges构建邻接表表示的树。

  2. 找到目标值:通过DFS遍历所有根到叶子的路径,找到最大的路径权值sum,这就是我们的目标值。

  3. 计算每个节点的最小增量:对于每个节点,我们需要计算为了使通过该节点的所有根到叶子路径都达到目标值,该节点需要的最小增量。

  4. 后序遍历计算答案:从叶子节点开始向上计算。对于每个节点:

    • 如果是叶子节点,其增量就是目标值减去从根到该叶子的当前路径和
    • 如果是内部节点,其增量是其所有子树中需要的最大增量
  5. 统计修改节点数:当一个节点的增量与其父节点的增量不同时,说明需要修改这个节点。

关键观察:

  • 只有当某个节点需要的增量与其父节点不同时,才需要实际增加该节点的权值
  • 我们可以通过一次DFS计算出每个节点的最小增量需求
  • 根节点总是需要被计算在内(除非所有路径本来就相等)

代码实现

class Solution {
public:
    int minIncrease(int n, vector<vector<int>>& edges, vector<int>& cost) {
        vector<vector<int>> graph(n);
        for (auto& edge : edges) {
            graph[edge[0]].push_back(edge[1]);
            graph[edge[1]].push_back(edge[0]);
        }
        
        long long maxPathSum = 0;
        
        function<void(int, int, long long)> findMaxPath = [&](int node, int parent, long long currentSum) {
            currentSum += cost[node];
            
            bool isLeaf = true;
            for (int child : graph[node]) {
                if (child != parent) {
                    isLeaf = false;
                    findMaxPath(child, node, currentSum);
                }
            }
            
            if (isLeaf) {
                maxPathSum = max(maxPathSum, currentSum);
            }
        };
        
        findMaxPath(0, -1, 0);
        
        int result = 0;
        
        function<long long(int, int)> dfs = [&](int node, int parent) -> long long {
            long long maxChildIncrease = 0;
            
            bool isLeaf = true;
            for (int child : graph[node]) {
                if (child != parent) {
                    isLeaf = false;
                    maxChildIncrease = max(maxChildIncrease, dfs(child, node));
                }
            }
            
            long long nodeIncrease = maxChildIncrease;
            if (isLeaf) {
                // For leaf nodes, calculate the increase needed
                long long pathSum = 0;
                int curr = node;
                int par = parent;
                
                function<void(int, int, long long)> calculatePath = [&](int n, int p, long long sum) {
                    sum += cost[n];
                    if (n == node) {
                        pathSum = sum;
                        return;
                    }
                    for (int child : graph[n]) {
                        if (child != p) {
                            calculatePath(child, n, sum);
                            if (pathSum != 0) return;
                        }
                    }
                };
                
                calculatePath(0, -1, 0);
                nodeIncrease = maxPathSum - pathSum;
            }
            
            if (parent == -1 || nodeIncrease != maxChildIncrease) {
                result++;
            }
            
            return nodeIncrease;
        };
        
        dfs(0, -1);
        return result;
    }
};
class Solution:
    def minIncrease(self, n: int, edges: List[List[int]], cost: List[int]) -> int:
        from collections import defaultdict
        
        graph = defaultdict(list)
        for u, v in edges:
            graph[u].append(v)
            graph[v].append(u)
        
        max_path_sum = 0
        
        def find_max_path(node, parent, current_sum):
            nonlocal max_path_sum
            current_sum += cost[node]
            
            is_leaf = True
            for child in graph[node]:
                if child != parent:
                    is_leaf = False
                    find_max_path(child, node, current_sum)
            
            if is_leaf:
                max_path_sum = max(max_path_sum, current_sum)
        
        find_max_path(0, -1, 0)
        
        result = 0
        
        def dfs(node, parent):
            nonlocal result
            
            max_child_increase = 0
            is_leaf = True
            
            for child in graph[node]:
                if child != parent:
                    is_leaf = False
                    max_child_increase = max(max_child_increase, dfs(child, node))
            
            if is_leaf:
                # Calculate path sum from root to this leaf
                def get_path_sum(curr, par, current_sum):
                    current_sum += cost[curr]
                    if curr == node:
                        return current_sum
                    
                    for child in graph[curr]:
                        if child != par:
                            path_sum = get_path_sum(child, curr, current_sum)
                            if path_sum != -1:
                                return path_sum
                    return -1
                
                path_sum = get_path_sum(0, -1, 0)
                node_increase = max_path_sum - path_sum
            else:
                node_increase = max_child_increase
            
            if parent == -1 or node_increase != max_child_increase:
                result += 1
            
            return node_increase
        
        dfs(0, -1)
        return result
public class Solution {
    public int MinIncrease(int n, int[][] edges, int[] cost) {
        List<int>[] graph = new List<int>[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new List<int>();
        }
        
        foreach (var edge in edges) {
            graph[edge[0]].Add(edge[1]);
            graph[edge[1]].Add(edge[0]);
        }
        
        long maxPathSum = 0;
        
        void FindMaxPath(int node, int parent, long currentSum) {
            currentSum += cost[node];
            
            bool isLeaf = true;
            foreach (int child in graph[node]) {
                if (child != parent) {
                    isLeaf = false;
                    FindMaxPath(child, node, currentSum);
                }
            }
            
            if (isLeaf) {
                maxPathSum = Math.Max(maxPathSum, currentSum);
            }
        }
        
        FindMaxPath(0, -1, 0);
        
        int result = 0;
        
        long DFS(int node, int parent) {
            long maxChildIncrease = 0;
            bool isLeaf = true;
            
            foreach (int child in graph[node]) {
                if (child != parent) {
                    isLeaf = false;
                    maxChildIncrease = Math.Max(maxChildIncrease, DFS(child, node));
                }
            }
            
            long nodeIncrease;
            if (isLeaf) {
                long GetPathSum(int curr, int par, long currentSum) {
                    currentSum += cost[curr];
                    if (curr == node) {
                        return currentSum;
                    }
                    
                    foreach (int child in graph[curr]) {
                        if (child != par) {
                            long pathSum = GetPathSum(child, curr, currentSum);
                            if (pathSum != -1) {
                                return pathSum;
                            }
                        }
                    }
                    return -1;
                }
                
                long pathSum = GetPathSum(0, -1, 0);
                nodeIncrease = maxPathSum - pathSum;
            } else {
                nodeIncrease = maxChildIncrease;
            }
            
            if (parent == -1 || nodeIncrease != maxChildIncrease) {
                result++;
            }
            
            return nodeIncrease;
        }
        
        DFS(0, -1);
        return result;
    }
}
var minIncrease = function(n, edges, cost) {
    const adj = Array(n).fill().map(() => []);
    for (const [u, v] of edges) {
        adj[u].push(v);
        adj[v].push(u);
    }
    
    let result = 0;
    
    function dfs(node, parent) {
        const children = adj[node].filter(child => child !== parent);
        
        if (children.length === 0) {
            return cost[node];
        }
        
        const childScores = children.map(child => dfs(child, node));
        const maxScore = Math.max(...childScores);
        
        for (const score of childScores) {
            if (score < maxScore) {
                result++;
            }
        }
        
        return cost[node] + maxScore;
    }
    
    dfs(0, -1);
    return result;
};

复杂度分析

复杂度类型复杂度说明
时间复杂度O(n²)需要遍历树两次,每次叶子节点都要计算到根的路径
空间复杂度O(n)递归调用栈和图的存储空间