Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
You are given an undirected tree rooted at node 0, with n nodes numbered from 0 to n - 1. The tree is represented by a 2D integer array edges of length n - 1, where edges[i] = [ui, vi] indicates an edge between nodes ui and vi.
You are also given an integer array nums of length n, where nums[i] represents the value at node i, and an integer k.
You may perform inversion operations on a subset of nodes subject to the following rules:
Subtree Inversion Operation:
When you invert a node, every value in the subtree rooted at that node is multiplied by -1.
Distance Constraint on Inversions:
You may only invert a node if it is "sufficiently far" from any other inverted node.
Specifically, if you invert two nodes a and b such that one is an ancestor of the other (i.e., if LCA(a, b) = a or LCA(a, b) = b), then the distance (the number of edges on the unique path between them) must be at least k.
Return the maximum possible sum of the tree's node values after applying inversion operations.
Example 1:
Input: edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]], nums = [4,-8,-6,3,7,-2,5], k = 2
Output: 27
Explanation:
nums array is [-4, 8, 6, 3, 7, 2, 5], and the total sum is 27.Example 2:
Input: edges = [[0,1],[1,2],[2,3],[3,4]], nums = [-1,3,-2,4,-5], k = 2
Output: 9
Explanation:
nums array becomes [-1, 3, -2, 4, 5], and the total sum is 9.Example 3:
Input: edges = [[0,1],[0,2]], nums = [0,-1,-2], k = 3
Output: 3
Explanation:
Apply inversion operations at nodes 1 and 2.
Constraints:
2 <= n <= 5 * 104edges.length == n - 1edges[i] = [ui, vi]0 <= ui, vi < nnums.length == n-5 * 104 <= nums[i] <= 5 * 1041 <= k <= 50edges represents a valid tree.Problem summary: You are given an undirected tree rooted at node 0, with n nodes numbered from 0 to n - 1. The tree is represented by a 2D integer array edges of length n - 1, where edges[i] = [ui, vi] indicates an edge between nodes ui and vi. You are also given an integer array nums of length n, where nums[i] represents the value at node i, and an integer k. You may perform inversion operations on a subset of nodes subject to the following rules: Subtree Inversion Operation: When you invert a node, every value in the subtree rooted at that node is multiplied by -1. Distance Constraint on Inversions: You may only invert a node if it is "sufficiently far" from any other inverted node. Specifically, if you invert two nodes a and b such that one is an ancestor of the other (i.e., if LCA(a, b) = a or LCA(a, b) = b), then the distance (the number of edges on the unique path between them) must be at least k.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Dynamic Programming · Tree
[[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]] [4,-8,-6,3,7,-2,5] 2
[[0,1],[1,2],[2,3],[3,4]] [-1,3,-2,4,-5] 2
[[0,1],[0,2]] [0,-1,-2] 3
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #3544: Subtree Inversion Sum
class Solution {
public long subtreeInversionSum(int[][] edges, int[] nums, int k) {
final int n = edges.length + 1;
int[] parent = new int[n];
List<Integer>[] graph = new List[n];
Arrays.fill(parent, -1);
Arrays.setAll(graph, i -> new ArrayList<>());
for (int[] edge : edges) {
final int u = edge[0];
final int v = edge[1];
graph[u].add(v);
graph[v].add(u);
}
return dfs(graph, /*u=*/0, /*stepsSinceInversion=*/k,
/*inverted=*/false, nums, k, parent, new Long[n][k + 1][2]);
}
private long dfs(List<Integer>[] graph, int u, int stepsSinceInversion, boolean inverted,
int[] nums, int k, int[] parent, Long[][][] mem) {
if (mem[u][stepsSinceInversion][inverted ? 1 : 0] != null)
return mem[u][stepsSinceInversion][inverted ? 1 : 0];
long num = inverted ? -nums[u] : nums[u];
long negNum = -num;
for (final int v : graph[u]) {
if (v == parent[u])
continue;
parent[v] = u;
num += dfs(graph, v, Math.min(k, stepsSinceInversion + 1), inverted, nums, k, parent, mem);
if (stepsSinceInversion == k)
negNum += dfs(graph, v, 1, !inverted, nums, k, parent, mem);
}
return mem[u][stepsSinceInversion][inverted ? 1 : 0] =
(stepsSinceInversion == k) ? Math.max(num, negNum) : num;
}
}
// Accepted solution for LeetCode #3544: Subtree Inversion Sum
package main
import "math"
// https://space.bilibili.com/206214
func subtreeInversionSum1(edges [][]int, nums []int, k int) int64 {
n := len(nums)
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)
}
memo := make([][][2]int, n)
for i := range memo {
memo[i] = make([][2]int, k)
for j := range memo[i] {
for p := range memo[i][j] {
memo[i][j][p] = math.MinInt
}
}
}
var dfs func(int, int, int, int) int
dfs = func(x, fa, cd, parity int) int {
p := &memo[x][cd][parity]
if *p != math.MinInt {
return *p
}
// 不反转
res := nums[x] * (1 - parity*2)
for _, y := range g[x] {
if y != fa {
res += dfs(y, x, max(cd-1, 0), parity)
}
}
// 反转
if cd == 0 {
s := nums[x] * (parity*2 - 1)
for _, y := range g[x] {
if y != fa {
s += dfs(y, x, k-1, parity^1) // 重置 CD
}
}
res = max(res, s)
}
*p = res
return res
}
return int64(dfs(0, -1, 0, 0))
}
func subtreeInversionSum2(edges [][]int, nums []int, k int) int64 {
n := len(nums)
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)
}
var dfs func(int, int) [][2]int
dfs = func(x, fa int) [][2]int {
v := nums[x]
res := make([][2]int, k)
for cd := range res {
res[cd] = [2]int{v, -v}
}
s0, s1 := -v, v
for _, y := range g[x] {
if y == fa {
continue
}
fy := dfs(y, x)
// 不反转
for cd := range res {
res[cd][0] += fy[max(cd-1, 0)][0]
res[cd][1] += fy[max(cd-1, 0)][1]
}
// 反转
s0 += fy[k-1][1]
s1 += fy[k-1][0]
}
// 反转
res[0][0] = max(res[0][0], s0)
res[0][1] = max(res[0][1], s1)
return res
}
return int64(dfs(0, -1)[0][0])
}
func subtreeInversionSum(edges [][]int, nums []int, k int) int64 {
n := len(nums)
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)
}
f := [][2]int{}
var dfs func(int, int) (int, int, int)
dfs = func(x, fa int) (int, int, int) {
f = append(f, [2]int{}) // 用于刷表
s := nums[x] // 子树和
notInv0, notInv1 := 0, 0 // 不反转 x 时的额外增量(0 表示上面反转了偶数次,1 表示上面反转了奇数次)
for _, y := range g[x] {
if y == fa {
continue
}
sy, y0, y1 := dfs(y, x)
s += sy
// 不反转 x,反转次数的奇偶性不变
notInv0 += y0
notInv1 += y1
}
subRes := f[len(f)-1] // 被刷表后的结果
f = f[:len(f)-1]
// 反转 x
// x 上面反转了偶数次,反转 x 会带来 -2 倍子树和的增量,且对于 x 的 k 级后代来说,上面反转了奇数次(所以是 subRes1)
inv0 := subRes[1] - s*2
// x 上面反转了奇数次,反转 x 会带来 2 倍子树和的增量,且对于 x 的 k 级后代来说,上面反转了偶数次(所以是 subRes0)
inv1 := subRes[0] + s*2
res0 := max(notInv0, inv0)
res1 := max(notInv1, inv1)
// 刷表法:更新 x 的 k 级祖先的状态
if len(f) >= k {
f[len(f)-k][0] += res0
f[len(f)-k][1] += res1
}
return s, res0, res1
}
s, res0, _ := dfs(0, -1)
return int64(s + res0) // 对于根节点来说,上面一定反转了偶数次(0 次)
}
# Accepted solution for LeetCode #3544: Subtree Inversion Sum
class Solution:
def subtreeInversionSum(
self,
edges: list[list[int]],
nums: list[int],
k: int
) -> int:
n = len(edges) + 1
parent = [-1] * n
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
@functools.lru_cache(None)
def dp(u: int, stepsSinceInversion: int, inverted: bool) -> int:
"""
Returns the maximum sum for subtree rooted at u, with
`stepsSinceInversion` steps of inversion and `inverted` is true if the
subtree is inverted.
"""
num = -nums[u] if inverted else nums[u]
negNum = -num
for v in graph[u]:
if v == parent[u]:
continue
parent[v] = u
num += dp(v, min(k, stepsSinceInversion + 1), inverted)
if stepsSinceInversion == k:
negNum += dp(v, 1, not inverted)
return max(num, negNum) if stepsSinceInversion == k else num
return dp(0, k, False)
// Accepted solution for LeetCode #3544: Subtree Inversion Sum
// Rust example auto-generated from java reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (java):
// // Accepted solution for LeetCode #3544: Subtree Inversion Sum
// class Solution {
// public long subtreeInversionSum(int[][] edges, int[] nums, int k) {
// final int n = edges.length + 1;
// int[] parent = new int[n];
// List<Integer>[] graph = new List[n];
// Arrays.fill(parent, -1);
// Arrays.setAll(graph, i -> new ArrayList<>());
//
// for (int[] edge : edges) {
// final int u = edge[0];
// final int v = edge[1];
// graph[u].add(v);
// graph[v].add(u);
// }
//
// return dfs(graph, /*u=*/0, /*stepsSinceInversion=*/k,
// /*inverted=*/false, nums, k, parent, new Long[n][k + 1][2]);
// }
//
// private long dfs(List<Integer>[] graph, int u, int stepsSinceInversion, boolean inverted,
// int[] nums, int k, int[] parent, Long[][][] mem) {
// if (mem[u][stepsSinceInversion][inverted ? 1 : 0] != null)
// return mem[u][stepsSinceInversion][inverted ? 1 : 0];
// long num = inverted ? -nums[u] : nums[u];
// long negNum = -num;
// for (final int v : graph[u]) {
// if (v == parent[u])
// continue;
// parent[v] = u;
// num += dfs(graph, v, Math.min(k, stepsSinceInversion + 1), inverted, nums, k, parent, mem);
// if (stepsSinceInversion == k)
// negNum += dfs(graph, v, 1, !inverted, nums, k, parent, mem);
// }
// return mem[u][stepsSinceInversion][inverted ? 1 : 0] =
// (stepsSinceInversion == k) ? Math.max(num, negNum) : num;
// }
// }
// Accepted solution for LeetCode #3544: Subtree Inversion Sum
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #3544: Subtree Inversion Sum
// class Solution {
// public long subtreeInversionSum(int[][] edges, int[] nums, int k) {
// final int n = edges.length + 1;
// int[] parent = new int[n];
// List<Integer>[] graph = new List[n];
// Arrays.fill(parent, -1);
// Arrays.setAll(graph, i -> new ArrayList<>());
//
// for (int[] edge : edges) {
// final int u = edge[0];
// final int v = edge[1];
// graph[u].add(v);
// graph[v].add(u);
// }
//
// return dfs(graph, /*u=*/0, /*stepsSinceInversion=*/k,
// /*inverted=*/false, nums, k, parent, new Long[n][k + 1][2]);
// }
//
// private long dfs(List<Integer>[] graph, int u, int stepsSinceInversion, boolean inverted,
// int[] nums, int k, int[] parent, Long[][][] mem) {
// if (mem[u][stepsSinceInversion][inverted ? 1 : 0] != null)
// return mem[u][stepsSinceInversion][inverted ? 1 : 0];
// long num = inverted ? -nums[u] : nums[u];
// long negNum = -num;
// for (final int v : graph[u]) {
// if (v == parent[u])
// continue;
// parent[v] = u;
// num += dfs(graph, v, Math.min(k, stepsSinceInversion + 1), inverted, nums, k, parent, mem);
// if (stepsSinceInversion == k)
// negNum += dfs(graph, v, 1, !inverted, nums, k, parent, mem);
// }
// return mem[u][stepsSinceInversion][inverted ? 1 : 0] =
// (stepsSinceInversion == k) ? Math.max(num, negNum) : num;
// }
// }
Use this to step through a reusable interview workflow for this problem.
Pure recursion explores every possible choice at each step. With two choices per state (take or skip), the decision tree has 2ⁿ leaves. The recursion stack uses O(n) space. Many subproblems are recomputed exponentially many times.
Each cell in the DP table is computed exactly once from previously solved subproblems. The table dimensions determine both time and space. Look for the state variables — each unique combination of state values is one cell. Often a rolling array can reduce space by one dimension.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Wrong move: An incomplete state merges distinct subproblems and caches incorrect answers.
Usually fails on: Correctness breaks on cases that differ only in hidden state.
Fix: Define state so each unique subproblem maps to one DP cell.
Wrong move: Recursive traversal assumes children always exist.
Usually fails on: Leaf nodes throw errors or create wrong depth/path values.
Fix: Handle null/base cases before recursive transitions.