Python 2440.py
class Solution:
def componentValue(self, nums: List[int], edges: List[List[int]]) -> int:
size = len(nums)
total = sum(nums)
next = [ list() for _ in range(size) ]
for a, b in edges:
next[a].append(b)
next[b].append(a)
def deeper(node, previous):
have = nums[node]
for n in next[node]:
if n != previous:
have += deeper(n, node)
if have > limit:
break
if have == limit:
have = 0 # delete edge
return have
for cuts in reversed(range(1, size)):
limit, remaining = divmod(total, cuts + 1)
if remaining == 0 and deeper(0, -1) == 0: # start at any node, pick 0
return cuts
return 0
Difficulty
and has an acceptance rate of about 54% (20k+ submissions).
Typical patterns to solve this problem:
- Array
- Depth First Search
- Enumeration
- Math
- Tree
AI Disclaimer
However, two years later I set up this website and used various AI tools to generate most of the walkthrough / explanations below.
See this dedicated page for more details.
Code Walkthrough
Overview
The componentValue method receives a list of node values (nums) and a list of undirected edges that define a tree. Its goal is to determine the largest number of edges that can be removed so that every resulting connected component has the same total value. The algorithm works by trying every possible number of cuts, from the maximum down to zero, and checking whether the tree can be partitioned into equal-sum components for the current candidate.
Key Steps
Building the adjacency structure
The first phase transforms the edge list into an adjacency list named next. This structure enables constant-time access to a node’s neighbours during the depth-first traversal.
size = len(nums)
total = sum(nums)
next = [ list() for _ in range(size) ]
for a, b in edges:
next[a].append(b)
next[b].append(a)
Recursive aggregation of subtree sums
The nested function deeper performs a post-order depth-first search. It accumulates the sum of the current node and all descendants that have not been cut off. If the accumulated sum exceeds the target limit, the loop breaks early, preventing unnecessary work. When the accumulated sum exactly matches limit, the function resets the sum to zero, effectively “deleting” the edge that would connect this subtree to its parent.
def deeper(node, previous):
have = nums[node]
for n in next[node]:
if n != previous:
have += deeper(n, node)
if have > limit:
break
if have == limit:
have = 0 # delete edge
return have
Trying all feasible numbers of cuts
The outer loop iterates over possible cut counts in descending order. For each cuts value, the algorithm computes the candidate component sum (limit) and verifies that the total sum is divisible by the number of components (cuts + 1). The divmod call returns both the quotient and the remainder in a single operation, saving an extra division. If the remainder is zero, the tree is tested with deeper. A return value of zero from deeper indicates that the entire tree can be split into components of size limit.
for cuts in reversed(range(1, size)):
limit, remaining = divmod(total, cuts + 1)
if remaining == 0 and deeper(0, -1) == 0: # start at any node, pick 0
return cuts
return 0
Edge Cases Handled
When the total sum of all node values is not divisible by the candidate number of components, the algorithm immediately discards that candidate because remaining will be non-zero. This prevents unnecessary traversal of the tree for impossible partitions.
If the tree consists of a single node, size equals one, the range(1, size) loop yields no iterations, and the method returns 0, correctly indicating that no cuts are possible.
During the depth-first aggregation, the early break if have > limit: break ensures that subtrees whose partial sum already exceeds the target are not explored further. This implicitly handles cases where a single subtree is too large to ever form a valid component, allowing the algorithm to abort the current candidate quickly.
The reset have = 0 when a subtree sum matches limit guarantees that the parent’s accumulation does not double-count a component that has already been isolated, which correctly supports scenarios where multiple adjacent subtrees each satisfy the target sum.
Performance Optimizations
The use of an adjacency list (next) provides O(1) neighbour lookup, which is essential for the recursive traversal.
The divmod function computes both the quotient and remainder in a single CPU instruction, eliminating a separate modulo operation and reducing overhead in the outer loop.
Iterating cuts in reverse order means the algorithm checks the largest possible number of cuts first. As soon as a feasible partition is found, the method returns, avoiding work on smaller cut counts.
Inside deeper, the early termination condition if have > limit: break prunes the recursion tree as soon as the partial sum exceeds the allowed component value. This prevents the function from descending into subtrees that cannot contribute to a valid partition, saving time especially in unbalanced trees with large values.
Finally, resetting the accumulator to zero when a component sum is reached (have = 0) eliminates the need for additional data structures to track cut edges, keeping the recursion lightweight and cache-friendly.
ELI5 - Simplified Explanation
Imagine you have a big pile of colorful building blocks connected by string to form a tree shape. Each block has a special number written on it. This program is like a clever helper trying to chop the string in just the right places so every single group of blocks adds up to the exact same total number.
First, the program figures out how many blocks there are and the total sum of all the numbers written on them. Then, it draws a map to remember which blocks are tied to each other so it knows how to travel along the string lines.
Because the program does not know how many groups the blocks should be split into, it tries every possibility. It starts by guessing maybe there are many tiny groups, or perhaps just a few big ones. For each guess, the program picks a target number that every group must reach.
To check if a guess works, the program goes on a little adventure starting from the very first block. It looks at the neighbors, adds up their numbers, and keeps moving until it hits that target total. When a group of blocks adds up to the magic number, the program pretends to cut the string right there and starts counting a fresh group. If all the blocks end up fitting perfectly into these sets with nothing left over, the program knows it found the right way to cut the tree.
Code Quality
Complexity Analysis
As a rule of thumb, the maximum input size for one second execution time is
- factorial: up to 10 elements
- exponential: up to 20 elements
- polynomial: a few thousand elements
- loglinear: up to 100,000 elements
- linear: a few million elements
- logarithmic: a billion elements
- constant: practically unlimited
Other Solutions
Obviously, I did that after I solved the problem myself.
In general, AlgoMonster and WalkCCC solutions are high-quality and always worth a look.
I haven't checked most of the other solutions, but they might be helpful as well.
And of course, the LeetCode forum is always a good place (or even the best) to look for alternative approaches.
(click on a column header to sort the table)
| author | |||||||
|---|---|---|---|---|---|---|---|
| author | solutions | ||||||
| www | AlgoMonster | C++ | Java | Python | TS | ||
| WalkCCC | C++ | Java | Python | ||||
| leetcode.ca | C++ | Java | Go | Python | |||
| Progiez | C++ | Java | Python | ||||
| github | charles-wangkai | Java | |||||
| everthis | JS | ||||||
| fsq | C++ | ||||||
| JoshCrozier | JS | ||||||
| wisdompeak | C++ | ||||||
| AlgoMonster | C++, Python, Java, TypeScript | ||||||
| WalkCCC | C++, Python, Java | ||||||
| leetcode.ca | C++, Go, Java, Python | ||||||
| Progiez | C++, Java, Python | ||||||
| charles-wangkai | Java | ||||||
| everthis | JavaScript | ||||||
| fsq | C++ | ||||||
| JoshCrozier | JavaScript | ||||||
| wisdompeak | C++ |
Videos
Similar Problems
- problem 663: Equal Tree Partition
- problem 2872: Maximum Number of K-Divisible Components
Extra
- Tech Stack - my development environment and tools
- AI - how AI helped me create this website (NOT solving the problems)
- Progress - progression over time
- Quality - code quality