Maximize Sum of Weights after Edge Removals
HardPrompt
There exists an undirected tree with n nodes numbered 0 to n - 1. You are given a 2D integer array edges of length n - 1, where edges[i] = [ui, vi, wi] indicates that there is an edge between nodes ui and vi with weight wi in the tree.
Your task is to remove zero or more edges such that:
- Each node has an edge with at most
kother nodes, wherekis given. - The sum of the weights of the remaining edges is maximized.
Return the maximum possible sum of weights for the remaining edges after making the necessary removals.
Example 1:
Input: edges = [[0,1,4],[0,2,2],[2,3,12],[2,4,6]], k = 2
Output: 22
Explanation:

- Node 2 has edges with 3 other nodes. We remove the edge
[0, 2, 2], ensuring that no node has edges with more thank = 2nodes. - The sum of weights is 22, and we can't achieve a greater sum. Thus, the answer is 22.
Example 2:
Input: edges = [[0,1,5],[1,2,10],[0,3,15],[3,4,20],[3,5,5],[0,6,10]], k = 3
Output: 65
Explanation:
- Since no node has edges connecting it to more than
k = 3nodes, we don't remove any edges. - The sum of weights is 65. Thus, the answer is 65.
Constraints:
2 <= n <= 1051 <= k <= n - 1edges.length == n - 1edges[i].length == 30 <= edges[i][0] <= n - 10 <= edges[i][1] <= n - 11 <= edges[i][2] <= 106- The input is generated such that
edgesform a valid tree.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach explores every possible subset of edges. For each subset, it checks if the degree constraint (each node connected to at most k other nodes) is satisfied for all nodes. If the constraint holds, the sum of weights of the edges in the subset is calculated and compared with the maximum sum found so far. This exhaustive search is implemented using recursion with backtracking.
Algorithm
- Define a recursive function
backtrack(edgeIndex, currentWeight, degrees). - The base case for the recursion is when
edgeIndexreaches the total number of edges. In the base case, check if thedegreesarray for all nodes satisfies the constraint (degree <= k). If it does, update the global maximum weight. If not, this path is invalid. - In the recursive step, for the edge at
edgeIndex, explore two branches:- Exclude the edge: Make a recursive call
backtrack(edgeIndex + 1, currentWeight, degrees). - Include the edge: Add its weight to
currentWeight, increment the degrees of its two endpoint nodes, and then make a recursive callbacktrack(edgeIndex + 1, newWeight, newDegrees). Remember to backtrack by undoing the changes todegreesafter the call returns.
- Exclude the edge: Make a recursive call
- Initialize the process by calling
backtrack(0, 0, new int[n]).
Walkthrough
The brute-force method systematically generates all 2^(n-1) subsets of edges. For each subset, we form a graph and verify if the degree of every node is at most k. If this condition is met, we compute the sum of weights of the edges in the current subset and update our answer with the maximum sum found.
This can be implemented with a recursive backtracking function. The function would iterate through each edge, making a decision to either include it in our set or exclude it. After considering all edges, we have a complete subset, which we then validate against the degree constraints.
class Solution { long maxWeight = 0; int[][] edges; int n; int k; public long maximumValueSum(int[][] edges, int k) { this.edges = edges; this.n = edges.length + 1; this.k = k; backtrack(0, 0, new int[n]); return maxWeight; } private void backtrack(int edgeIndex, long currentWeight, int[] degrees) { if (edgeIndex == edges.length) { for (int deg : degrees) { if (deg > k) { return; // Invalid configuration } } maxWeight = Math.max(maxWeight, currentWeight); return; } int u = edges[edgeIndex][0]; int v = edges[edgeIndex][1]; int weight = edges[edgeIndex][2]; // Case 1: Exclude the current edge backtrack(edgeIndex + 1, currentWeight, degrees); // Case 2: Include the current edge degrees[u]++; degrees[v]++; backtrack(edgeIndex + 1, currentWeight + weight, degrees); degrees[u]--; // backtrack degrees[v]--; // backtrack }}Complexity
Time
O(2^N * N). There are `n-1` edges, leading to `2^(n-1)` subsets. For each subset, we validate the degrees of `N` nodes, taking `O(N)` time.
Space
O(N) for the recursion stack depth and the `degrees` array.
Trade-offs
Pros
Simple to conceptualize and implement.
Cons
Extremely inefficient due to its exponential time complexity.
Will result in a 'Time Limit Exceeded' error for the given constraints.
This approach leverages the tree structure of the graph by using dynamic programming. A post-order traversal (DFS) is employed to compute the solution for subtrees. For each node u, we calculate the maximum possible weight sum for the subtree rooted at u under two distinct scenarios: one where the edge connecting u to its parent is kept, and another where it's dropped. This allows decisions to be propagated up the tree correctly.
Algorithm
- Build an adjacency list for the tree.
- Start a DFS from an arbitrary root (e.g., node 0). The function
dfs(u, parent)will perform a post-order traversal. - For each node
u,dfs(u, parent)returns a pair of values:[max_sum_if_parent_edge_kept, max_sum_if_parent_edge_dropped]. - In the post-order step for
u(after visiting all its children):- Initialize a
base_sumby summing up thedroppedvalues from all children's results. - For each child
v, calculate the 'gain' of keeping the edge(u, v):gain = weight(u, v) + kept_v - dropped_v. - Collect all these gains in a list.
- Sort the
child_gainslist in descending order. - Calculate
res_dropped:base_sum+ sum of the topkpositive gains. - Calculate
res_kept:base_sum+ sum of the topk-1positive gains.
- Initialize a
- The final answer is the
droppedvalue returned by the initialdfs(root, -1)call.
Walkthrough
We first represent the tree using an adjacency list. Then, we perform a DFS from an arbitrary root (e.g., node 0). The core of this method is a function, dfs(u, parent), which computes results for the subtree at u after all its children's subtrees have been processed.
This function returns two values for node u:
res_kept: The maximum weight sum inu's subtree, assuming the edge to its parentpis kept.res_dropped: The maximum weight sum inu's subtree, assuming the edge topis dropped.
To compute these, we first recursively call dfs on all children v of u. We aggregate the dropped results from all children into a base_sum. Then, for each child v, we calculate the potential 'gain' from keeping the edge (u, v), which is weight(u, v) + v_kept - v_dropped. We collect these gains, sort them in descending order, and add the top positive gains to base_sum based on the degree budget at u. If the parent edge is dropped, u can connect to k children. If it's kept, it can connect to k-1 children.
class Solution { private List<List<int[]>> adj; private int k; public long maximumValueSum(int[][] edges, int k) { int n = edges.length + 1; this.adj = new ArrayList<>(n); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } for (int[] edge : edges) { adj.get(edge[0]).add(new int[]{edge[1], edge[2]}); adj.get(edge[1]).add(new int[]{edge[0], edge[2]}); } this.k = k; long[] result = dfs(0, -1); return result[1]; // [0] is kept, [1] is dropped } // Returns a pair: [max_sum_if_parent_edge_kept, max_sum_if_parent_edge_dropped] private long[] dfs(int u, int p) { List<Long> childGains = new ArrayList<>(); long baseSumFromChildren = 0; for (int[] edgeInfo : adj.get(u)) { int v = edgeInfo[0]; int weight = edgeInfo[1]; if (v == p) continue; long[] childResult = dfs(v, u); long keptV = childResult[0]; long droppedV = childResult[1]; baseSumFromChildren += droppedV; childGains.add((long)weight + keptV - droppedV); } Collections.sort(childGains, Collections.reverseOrder()); long resKept = baseSumFromChildren; long resDropped = baseSumFromChildren; for (int i = 0; i < childGains.size(); i++) { long gain = childGains.get(i); if (gain <= 0) break; if (i < k) { // Can keep up to k edges if parent edge is dropped resDropped += gain; } if (i < k - 1) { // Can keep up to k-1 edges if parent edge is kept resKept += gain; } } return new long[]{resKept, resDropped}; }}Complexity
Time
O(N log N). The DFS visits each node and edge once. At a node `u` with degree `d`, sorting the `d-1` child gains takes `O(d log d)`. The sum of `d log d` over all nodes is bounded by `O(N log N)`.
Space
O(N). Required for the adjacency list, the recursion stack (which can go up to `O(N)` deep), and the temporary list for child gains (which can be up to `O(N)` for a star graph).
Trade-offs
Pros
Efficient and correct for the given constraints.
Systematically solves the problem by breaking it down into subproblems on the tree.
Cons
The sorting step at each node can be a bottleneck for nodes with a very high degree, making it slightly less optimal than the heap-based approach.
This approach is an optimization of the Tree DP method. Instead of sorting the list of child gains at each node, which takes O(d log d) time for a node with d children, we use a min-heap to find the top k gains more efficiently. This reduces the time spent at each node from O(d log d) to O(d log k), leading to a better overall time complexity.
Algorithm
- The overall DP state and traversal logic are the same as the sorting-based approach.
- The key change is in how the top gains are selected at each node
u. - Instead of collecting all child gains and sorting, we process them as they are computed.
- We maintain a min-heap of size at most
kand a running sum of the gains in the heap. - For each child
v, calculate its gain. If the gain is positive:- Add the gain to the heap and the running sum.
- If the heap size exceeds
k, remove the smallest element (the root) from the heap and subtract it from the running sum.
- After processing all children, the running sum will be the total of the top
kpositive gains (gain_sum_for_dropped). - To find the sum of the top
k-1gains (gain_sum_for_kept), we check if the heap size is exactlyk. If so, we subtract the smallest element (the heap's root) fromgain_sum_for_dropped.
Walkthrough
The fundamental dynamic programming structure remains the same. We still perform a post-order traversal and compute (kept, dropped) values for each subtree. The optimization lies in avoiding the O(d log d) sorting step at each node.
At a node u, as we compute the gain from each child v, we use a min-heap of size k to maintain the k largest gains encountered so far. For each positive gain, we add it to the heap. If the heap's size grows beyond k, we evict the smallest element. This process takes O(log k) time per child.
After iterating through all children of u, the heap contains the top k positive gains. The sum of these gains is the value we add to base_sum for the res_dropped case. For the res_kept case, we need the sum of the top k-1 gains. If the heap's size is k, this is simply the total sum minus the smallest element in the heap (the root). If the heap's size is less than k, the sum for both cases is the same.
class Solution { private List<List<int[]>> adj; private int k; public long maximumValueSum(int[][] edges, int k) { int n = edges.length + 1; this.adj = new ArrayList<>(n); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } for (int[] edge : edges) { adj.get(edge[0]).add(new int[]{edge[1], edge[2]}); adj.get(edge[1]).add(new int[]{edge[0], edge[2]}); } this.k = k; long[] result = dfs(0, -1); return result[1]; } private long[] dfs(int u, int p) { long baseSumFromChildren = 0; PriorityQueue<Long> topGainsHeap = new PriorityQueue<>(); long topGainsSum = 0; for (int[] edgeInfo : adj.get(u)) { int v = edgeInfo[0]; if (v == p) continue; int weight = edgeInfo[1]; long[] childResult = dfs(v, u); long keptV = childResult[0]; long droppedV = childResult[1]; baseSumFromChildren += droppedV; long gain = (long)weight + keptV - droppedV; if (gain > 0) { topGainsHeap.add(gain); topGainsSum += gain; if (topGainsHeap.size() > k) { topGainsSum -= topGainsHeap.poll(); } } } long resDropped = baseSumFromChildren + topGainsSum; long resKept = resDropped; if (topGainsHeap.size() == k) { resKept -= topGainsHeap.peek(); } return new long[]{resKept, resDropped}; }}Complexity
Time
O(N log k). The DFS visits each node once. At each node `u` with `d` children, we perform `d` heap operations, each taking `O(log k)` time. The total time is `Sum(d * log k)` over all nodes, which simplifies to `O(N log k)`.
Space
O(N). For the adjacency list, recursion stack, and the heaps. The total size of all heaps on the call stack is bounded by `O(N)`.
Trade-offs
Pros
The most efficient solution for this problem.
Improves upon the sorting approach, especially when
kis small compared toN.
Cons
The implementation can be slightly more complex than the sorting approach.
Solutions
Solution
class Solution {private List<int[]>[] g;private int k;public long maximizeSumOfWeights(int[][] edges, int k) { this.k = k; int n = edges.length + 1; g = new List[n]; Arrays.setAll(g, i->new ArrayList<>()); for (var e : edges) { int u = e[0], v = e[1], w = e[2]; g[u].add(new int[]{v, w}); g[v].add(new int[]{u, w}); } var ans = dfs(0, -1); return Math.max(ans[0], ans[1]); }private long[] dfs(int u, int fa) { long s = 0; List<Long> t = new ArrayList<>(); for (var e : g[u]) { int v = e[0], w = e[1]; if (v == fa) { continue; } var res = dfs(v, u); s += res[0]; long d = w + res[1] - res[0]; if (d > 0) { t.add(d); } } t.sort(Comparator.reverseOrder()); for (int i = 0; i < Math.min(t.size(), k - 1); ++i) { s += t.get(i); } return new long[]{s + (t.size() >= k ? t.get(k - 1) : 0), s}; }}class Solution {public: long long maximizeSumOfWeights(vector<vector<int>> &edges, int k) { int n = edges.size() + 1; vector<vector<pair<int, int>>> g(n); for (auto &e : edges) { int u = e[0], v = e[1], w = e[2]; g[u].emplace_back(v, w); g[v].emplace_back(u, w); } using ll = long long; auto dfs = [&](auto &&dfs, int u, int fa) -> pair<ll, ll> { ll s = 0; vector<ll> t; for (auto &[v, w] : g[u]) { if (v == fa) { continue; } auto [a, b] = dfs(dfs, v, u); s += a; ll d = w + b - a; if (d > 0) { t.push_back(d); } } ranges ::sort(t, greater<>()); for (int i = 0; i < min((int)t.size(), k - 1); ++i) { s += t[i]; } return {s + (t.size() >= k ? t[k - 1] : 0), s}; }; auto [x, y] = dfs(dfs, 0, -1); return max(x, y); }};class Solution: def maximizeSumOfWeights(self, edges: List[List[int]], k: int) -> int: def dfs(u: int, fa: int) -> Tuple[int, int]: s = 0 t = [] for v, w in g[u]: if v == fa: continue a, b = dfs(v, u) s += a if (d: = (w + b - a)) > 0: t . append(d) t . sort(reverse=True) return s + sum(t[: k]), s + sum(t[: k - 1]) n = len(edges) + 1 g: List[List[Tuple[int, int]]] = [[] for _ in range(n)] for u, v, w in edges: g[u]. append((v, w)) g[v]. append((u, w)) x, y = dfs(0, - 1) return max(x, y)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.