339. Nested List Weight Sum - Detailed Explanation
Problem Statement
Given a nested list of integers nestedList
, where each element is either an integer or a list (which can contain more integers or lists), return the sum of all integers in the list weighted by their depth. The depth of an integer is the number of lists that enclose it. For example, in the nested list [1,[2,2],[[3],2],1]
, each integer’s value is set to its depth (1 is at depth 1, 2's inside one list are depth 2, 3 is inside two lists so depth 3, etc.). An integer at the top level has depth 1.
Goal: Calculate sum(integer * depth)
for every integer in the nested list.
Example 1:
- Input:
nestedList = [[1,1],2,[1,1]]
- Output:
10
- Explanation: There are four
1
's at depth 2 and one2
at depth 1. So the weighted sum is1*2 + 1*2 + 1*2 + 1*2 + 2*1 = 10
.
Example 2:
- Input:
nestedList = [1,[4,[6]]]
- Output:
27
- Explanation:
1
is at depth 1,4
is at depth 2, and6
is at depth 3. The result is1*1 + 4*2 + 6*3 = 27
.
Example 3:
- Input:
nestedList = [0]
- Output:
0
(the single integer0
is at depth 1, so0*1 = 0
).
Constraints:
1 <= nestedList.length <= 50
- The integer values are in the range
[-100, 100]
. - The maximum depth of any integer is ≤ 50.
Hints
-
Recursion Idea: Try using recursion to traverse the nested structure. When you go into a sublist, you increase the depth by 1. Keep track of the current depth and accumulate
value * depth
for integers. -
Use a Stack or Queue: If recursion seems tricky or could cause deep call stacks, you can simulate the process using a stack (for depth-first traversal) or a queue (for breadth-first traversal). Store each element along with its depth.
-
Depth Tracking: Ensure you correctly increment the depth when moving into inner lists and decrement (or pop) when backtracking. Each nested level should contribute an extra weight to the integers in it.
-
Avoid Redundant Work: A naive approach might recompute an element’s depth multiple times. Instead, carry the depth as you traverse so each integer’s depth is determined in one pass. This will avoid unnecessary recalculations and improve efficiency.
Solution Approaches
We will discuss multiple approaches to solve the problem, from a brute-force two-pass method to more efficient single-pass traversals using DFS and BFS. All approaches ultimately have linear time complexity in the number of nested elements, but they differ in implementation style and space usage. For each approach, we'll provide both Python and Java implementations, and analyze their complexity.
1. Brute Force Solution (Two-Pass)
Idea:
This straightforward approach involves two passes through the nested list. In the first pass, we traverse the nested list and collect each integer along with its depth. In the second pass, we compute the weighted sum using the collected values. This avoids doing multiplication during the initial traversal, but it uses extra space to store intermediate results. It’s a clear but not the most optimal approach.
How it works: In the first traversal, we can use recursion or iteration to flatten the structure into a list of pairs (value, depth)
. In the second step, simply sum up value * depth
for each pair. This approach ensures we calculate each integer’s depth once, but we do store all values in memory before summing them.
Complexity:
- Time Complexity: O(n) where n is the total number of integers (and sublists) in
nestedList
. We visit each element in the first pass and do a constant amount of work (adding to a list), then visit each collected integer in the second pass for summation. This is effectively 2n operations, which is still O(n). - Space Complexity: O(n) extra space for storing the list of (value, depth) pairs. In the worst case (e.g., no nesting at all), we store all n integers with their depth. This is in addition to the recursion stack or iterative stack/queue used for traversal (which in worst case could be O(d) for depth or O(n) for breadth). The extra space makes this approach less memory-efficient than necessary.
Python Implementation:
Java Implementation:
Note: In the Java implementation, we stored pairs of value/depth in a list of int[]
. We could also create a simple Pair class or use AbstractMap.SimpleEntry<Integer,Integer>
for clarity. The approach remains the same.
2. Depth-First Search (DFS) - Recursive
Idea:
DFS recursive traversal is a natural way to solve this problem. We dive into nested lists depth-first, keeping track of the current depth, and accumulate the sum in one single pass. Each time we encounter an integer, we multiply it by the current depth and add to the total. If we encounter a list, we recursively process that list with depth + 1. This approach avoids any extra storage of intermediate results (unlike the brute-force approach) – we do calculations on the fly.
How it works: We define a recursive helper function that takes a list and the current depth as arguments. It iterates through each NestedInteger
in the list:
- If it’s an integer, multiply by the depth and add to the running sum.
- If it’s a list, recursively call the helper on that sublist with depth+1.
The recursion naturally handles the nested structure, and each recursive call deals with one level deeper in the list.
Complexity:
- Time Complexity: O(n), where n is the total number of nested elements (including all integers and lists). We visit each element exactly once in the recursion. Each integer contributes one multiplication and addition. This is as efficient as possible since we must examine all elements.
- Space Complexity: O(d) due to recursion stack, where d is the maximum nesting depth. In the worst case of a deeply nested list (e.g., a list containing a list containing a list... 50 levels deep), the recursion stack would go 50 calls deep (which is safe within the problem constraints). Aside from recursion space, we use only O(1) extra space to hold the running sum and some variables.
Python Implementation:
Java Implementation:
This recursive DFS solution is clean and easy to understand. The depth is carried along as a parameter, so we never have to recalculate how deep we are – we increment it as we go into sublists and decrement implicitly as we return from recursion.
3. Depth-First Search (DFS) - Iterative using Stack
Idea:
We can simulate the DFS traversal using an explicit stack data structure instead of recursion. This avoids potential recursion depth issues and can be easier to debug for some. The idea is to push elements onto the stack along with their current depth. We then process elements in LIFO (last-in-first-out) order, which naturally corresponds to a depth-first traversal.
How it works: Use a stack of pairs (NestedInteger, depth)
. Initially, push all elements of nestedList
onto the stack with depth 1. Then, loop while the stack is not empty:
- Pop an element (this gives the current
NestedInteger
and its depth). - If it’s an integer, add
value * depth
to the sum. - If it’s a list, push all of its child elements onto the stack with depth+1.
By pushing children of a list onto the stack, we ensure they will be processed before processing other siblings (which mimics recursion going deep into one branch before others). Note that the order in which we push matters only if we care about processing order. For summation, the order doesn’t affect the final result. If we want to preserve the natural order (left to right) as recursion would, we should push the children in reverse order onto the stack. This way, the leftmost child ends up on top of the stack and is processed first.
Complexity:
- Time Complexity: O(n), since each nested element is pushed and popped exactly once. We do constant work for each element (check type, maybe multiply and add, or push children).
- Space Complexity: O(n) in the worst case. In the most extreme scenario (e.g., a list containing all integers or a very broad nesting where one list contains many integers), the stack could hold all elements at once. If the nesting is more balanced or deep rather than broad, the stack will hold at most
d
elements at a time (where d is depth). In any case, the space is proportional to the size of input in worst-case.
Python Implementation:
Java Implementation:
In the Java solution above, we use two parallel stacks: one for the NestedInteger
nodes and one for their depths. We push corresponding entries to both. We could also create a small helper class to store (node, depth)
pairs in one stack for cleaner code. The logic remains the same.
4. Breadth-First Search (BFS) - Iterative using Queue
Idea:
Instead of depth-first, we can do a breadth-first traversal of the nested list. BFS processes all nodes at depth 1, then depth 2, and so on. In this approach, we use a queue to keep track of nodes to visit, along with their depths. This is similar to level-order traversal in a tree. When using BFS for this problem, an integer’s contribution can be added as soon as we encounter it (like DFS), given we know its depth from the level we are on.
How it works: Use a queue of pairs (NestedInteger, depth)
. Start by enqueuing all top-level elements with depth 1. Then repeat: dequeue an element, if it's an integer add value * depth
to the sum; if it's a list, enqueue all its children with depth+1. This way, we traverse level by level.
Complexity:
- Time Complexity: O(n) for the same reason as other approaches – each element is visited once and processed in O(1) time. BFS will potentially explore nodes in a different order than DFS, but it still touches each node once.
- Space Complexity: O(n) in the worst case. If a single list contains all integers (no nesting), the queue initially holds O(n) elements at depth 1. More generally, the queue size can grow to the maximum breadth of any level in the nested list. Depth storage is an integer per element in the queue (similar to the DFS iterative approach). In big-O terms, this is linear in the input size in the worst scenario.
Python Implementation:
Java Implementation:
The BFS approach explicitly tracks depth for each element via a parallel queue (depthQueue
). We could also combine the node and depth into a single object or use a small tuple-like class in Java. The concept remains: process layer by layer. BFS can be helpful if, for example, we wanted to compute something like "sum of all values at each depth" or handle the weights in reverse (as we'll see in the variation), but for the standard weight sum, BFS and DFS both achieve the same result with equal efficiency.
5. Optimized Approach: Single-Pass BFS by Level (No Redundant Depth Storage)
Idea:
This approach is a slight optimization over the standard BFS. The idea is to avoid storing a separate depth for each element by taking advantage of the level-order traversal structure. We will iterate level by level, maintaining a single depth
counter that increases each time we go to the next level. This eliminates the need to carry depth information with every element, thus avoiding any redundant storage or updates of depth for each element.
How it works: We perform BFS in rounds. For each depth level:
- Increase a
depth
counter (starting from 1 at the top level). - Process all elements that are currently in the queue (which represent all nodes at the current depth).
- For each element: if it’s an integer, add
value * depth
to the sum; if it’s a list, enqueue its children (these will be at the next depth level). - Once one level is fully processed, the next batch of elements in the queue are all one level deeper, so increment depth and continue.
This way, we don’t store the depth alongside each element; we know that everything we are processing in a given iteration has the same depth. This approach avoids an explicit depth structure (like a second queue or pair objects), making the algorithm slightly cleaner.
Complexity:
- Time Complexity: O(n), as we still visit each nested element exactly once in a single pass. The grouping by level doesn't change the overall work done.
- Space Complexity: O(n) in the worst case for the queue (same as normal BFS). We do save a bit of space by not storing a depth for every element, but this is a constant-factor improvement. Asymptotically, space is linear in the input size for broad structures. For deeply nested but narrow structures, space would be O(d) corresponding to the max depth of the queue at any time.
Python Implementation:
Java Implementation:
In this optimized BFS approach, we use the fact that we're processing in fixed-depth "waves." The variable depth
increases once per level, rather than being stored per element. This removes the slight redundancy of incrementing and storing depth for every single element. The code is concise and still easy to follow. It’s essentially the same complexity as the standard BFS solution, but it’s a nice demonstration of using level traversal to manage state globally.
Step-by-Step Example Walkthrough
Let’s walk through Example 2 (nestedList = [1,[4,[6]]]
) step by step to see how the algorithm (DFS recursive approach) processes it and accumulates the sum:
nestedList = [ 1, [4, [6]] ]
-
Start at depth 1: The list has two elements at the top level. Initialize
sum = 0
.- First element is
1
(an integer). Depth = 1, so add1 * 1
to sum.- Current sum = 1.
- Second element is
[4, [6]]
(a list). It’s not an integer, so we go one level deeper into this sublist.
- First element is
-
Depth 2 (inside the sublist [4, [6]]): Now we are examining the list
[4, [6]]
with depth = 2.- First element here is
4
(integer). Depth = 2, add4 * 2
to sum.- Current sum = 1 (previous) + 8 = 9.
- Second element is
[6]
(a list). Not an integer, go one level deeper into this sublist.
- First element here is
-
Depth 3 (inside the sublist [6]): We are now looking at the innermost list
[6]
at depth = 3.- It contains a single element
6
(integer). Depth = 3, add6 * 3
to sum.- Current sum = 9 (previous) + 18 = 27.
- This sublist has no more elements. We step out to the previous level.
- It contains a single element
-
We have finished processing all elements of the depth-2 list
[4, [6]]
. Step back out to depth 1 context. -
Back at depth 1, there are no more elements in the top-level list to process.
-
Final sum = 27. This matches the expected output.
The above trace demonstrates a DFS (depth-first) process. The algorithm keeps track of the current depth as it goes down into nested lists and back up. In an iterative DFS using a stack, the operations would occur in a similar order (though the exact sequence of pushing/popping might differ slightly).
If we used BFS (level-order) for the same example, the order of processing would be different, but the result would be the same:
- Depth 1: process
1
(add 1*1), encounter sublist[4,[6]]
(enqueue its contents for next level). - Depth 2: process
4
(add 4*2), encounter sublist[6]
(enqueue its contents). - Depth 3: process
6
(add 6*3). - Sum = 27.
Both approaches correctly accumulate the weighted sum.
Common Mistakes and Edge Cases
- Incorrect Depth Updates: A frequent bug is forgetting to increment the depth when going into a nested list, or using the wrong depth for an integer. Always increment depth as you go deeper, and if using recursion, be careful to not carry over depth incorrectly when backtracking (this is naturally handled if depth is a function parameter or a local variable). In an iterative solution, ensure that when you push a child element, you use
depth+1
. Forgetting these will lead to wrong weights. - Mixing up Depth vs. Weight in Variation: If you attempt the inverted weight variation (Nested List Weight Sum II), be cautious not to confuse the normal depth with the “inverted weight.” Many mistakes happen by using the depth directly instead of the
maxDepth - depth + 1
formula for weights, or by not updating the logic to accumulate weights properly. - Not Handling Empty Lists: The nested list might contain empty sublists (e.g.,
[1, [], [2,[ ]]]
). Your code should handle these gracefully (they contribute nothing to the sum and should simply be skipped over). Ensure that when you get a list, you iterate its children even if that list could be empty. Similarly, an entirenestedList
could theoretically be empty (though the problem constraints ensure at least 1 element in top list). An empty input should return 0. - Edge Case - Single Integer: If the input is just a single integer (e.g.,
[5]
with no nesting), the output should just be5
(depth 1). Make sure your code correctly handles the top-level integers. All approaches above do handle this, but if you wrote something fancy, double-check that a non-nested scenario works. - Using Global Variables in Recursion: If implementing recursively in a real interview or coding environment, watch out for using global variables for accumulating sum or depth. It’s safer to pass depth as a parameter or use a helper function that returns the sum. Globals can lead to errors if the function is used multiple times in the same program or if not reset properly.
- Stack/Queue Initialization: In iterative approaches, a common mistake is not initializing the stack/queue with all top-level elements, or initializing depth incorrectly. Ensure each top-level element is enqueued with depth 1 (or pushed with depth 1). If you only push the first element, you’ll miss others. Also, be careful with the order if that matters for your traversal logic.
Alternative Variation: Nested List Weight Sum II (Inverse Depth Weighting)
There is a follow-up variation of this problem, often known as Nested List Weight Sum II (LeetCode 364). In this variation, the weight of integers is reversed: instead of deeper integers having a larger weight, they have a smaller weight. Specifically, the weight is defined such that leaf-level (deepest) integers have weight 1, and the weight increases by 1 for each level you go up . Equivalently, if maxDepth
is the maximum depth of any integer in the list, an integer at depth d
in the original sense now has weight = maxDepth - d + 1
.
To clarify, consider the same examples with this inverted weighting:
- Example 1 (Inverse weights):
nestedList = [[1,1],2,[1,1]]
. HeremaxDepth = 2
. The four1
's are at depth 2, so their weight is2 - 2 + 1 = 1
. The integer2
is at depth 1, so its weight is2 - 1 + 1 = 2
. The sum becomes1*1 + 1*1 + 1*1 + 1*1 + 2*2 = 8
. - Example 2 (Inverse weights):
nestedList = [1,[4,[6]]]
. HeremaxDepth = 3
. The integer1
(depth 1) gets weight3 - 1 + 1 = 3
;4
(depth 2) gets weight3 - 2 + 1 = 2
;6
(depth 3) gets weight3 - 3 + 1 = 1
. The weighted sum is1*3 + 4*2 + 6*1 = 17
.
In other words, the shallower the integer, the larger its weight in this variation (the opposite of the original problem).
Approach to Solve (Two-Pass): A straightforward way to solve Nested List Weight Sum II is:
- Find
maxDepth
: Do a DFS (or BFS) traversal to determine the maximum depth of any integer in the nested list. This is similar to computing the maximum nesting level. - Compute Weighted Sum: Traverse the list again, this time using the formula
weight = maxDepth - currentDepth + 1
for each integer. Sum upvalue * weight
for all integers.
For example, in Python:
def depthSumInverse_two_pass(nestedList): # First pass to find maximum depth def get_max_depth(lst, depth): max_d = depth for ni in lst: if not ni.isInteger(): max_d = max(max_d, get_max_depth(ni.getList(), depth+1)) return max_d maxDepth = get_max_depth(nestedList, 1) # Second pass to compute the inverse weighted sum def helper(lst, depth): total = 0 for ni in lst: if ni.isInteger(): # weight = maxDepth - depth + 1 total += ni.getInteger() * (maxDepth - depth + 1) else: total += helper(ni.getList(), depth+1) return total return helper(nestedList, 1)
This two-pass method is clear: the first traversal finds how deep the nesting goes, and the second does the summation with appropriate weights. Its complexity is O(n) time and O(d) space, similar to the original problem (technically O(2n) ~ O(n) time due to two passes). Given the constraints, two passes are not a problem.
Optimized Approach (Single-Pass): We can actually solve the inverse weight sum in one pass using a clever BFS strategy. The trick is to accumulate the sum of integers at each depth in a running manner. Specifically:
- Traverse the nested list level by level (BFS).
- Keep track of two sums: an unweighted sum (sum of integers seen so far without weight) and a weighted sum (which will accumulate the final result).
- At each level of BFS, add all integers at that level to
unweighted
. After processing that level, add the entireunweighted
sum toweighted
. - Move to the next level and repeat.
Why does this work? In the end, unweighted
will have the sum of all integers (because we keep adding all integers level by level), and weighted
effectively adds the sum of integers once for each level from the bottom. An integer that is at depth d
will be added to weighted
in each level of the BFS from level d
up to maxDepth
. That means it gets added (maxDepth - d + 1)
times, which is exactly its weight in the inverted scheme.
For example, using this method on [1,[4,[6]]]
:
- Start with
unweighted = 0, weighted = 0
. - Level 1: elements =
[1, sublist]
.unweighted += 1
. After level,weighted += unweighted
(nowweighted = 1
). Enqueue sublist. - Level 2: elements =
[4, sub-sublist]
.unweighted += 4
(unweighted was 1, now becomes 5). After level,weighted += unweighted
(nowweighted = 1 + 5 = 6
). Enqueue sub-sublist. - Level 3: elements =
[6]
.unweighted += 6
(was 5, now 11 which is sum of all numbers). After level,weighted += unweighted
(weighted = 6 + 11 = 17). Done.weighted
is 17 which matches the answer.
Here is a Python implementation of the one-pass BFS approach:
from collections import deque def depthSumInverse_one_pass(nestedList): queue = deque(nestedList) unweighted = 0 weighted = 0 while queue: # process one level at a time for _ in range(len(queue)): ni = queue.popleft() if ni.isInteger(): unweighted += ni.getInteger() else: for child in ni.getList(): queue.append(child) # after processing this level, add the running sum (unweighted) to weighted result weighted += unweighted return weighted
In this code, unweighted
accumulates the sum of all integers seen so far (by the time we've gone through the whole list, unweighted
is just the sum of all integers). weighted
is increased by the current unweighted
after each level, effectively giving each integer an extra count for each level above it. The result is the inverse-weighted sum. The time complexity is O(n) and space O(n), similar to other approaches.
Both the two-pass and single-pass methods will yield the correct result for the inverse weight problem. The single-pass BFS is a neat trick specific to the inverse weighting scenario, leveraging the structure of BFS traversal to implicitly calculate weights.
Related Problems for Further Practice
To strengthen understanding of nested structures, depth calculations, and recursion, you can practice the following related problems:
- Flatten Nested List Iterator (LeetCode 341) – Implement an iterator that flattens a nested list of integers. This problem uses the same
NestedInteger
interface and requires managing a stack/recursion to flatten on the fly. - Nested List Weight Sum II (LeetCode 364) – The variation discussed above with reverse depth weights. It’s a direct follow-up to try out the two-pass and one-pass solutions described.
- Mini Parser (LeetCode 385) – Parse a string of a nested list and produce the NestedInteger object. This involves recursion/stack to build a nested structure from a string, which is good practice for understanding nested data representations.
- Employee Importance (LeetCode 690) – You’re given a data structure of employees with IDs, importance values, and subordinates (which form an organizational hierarchy). The task is to sum up an employee’s importance value and those of all their direct and indirect subordinates. This can be solved with DFS or BFS on a nested-like structure (graph traversal).
- Maximum Depth of Binary Tree (LeetCode 104) – Although this is about a binary tree, it’s a simpler recursion problem for depth. It helps practice the idea of depth calculation in a hierarchical structure (here, a tree) and can be a stepping stone to dealing with nested lists or more complex recursive structures.
GET YOUR FREE
Coding Questions Catalog
