From c498113aa1abe6c86d88d102beec2d0d395abc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=BD=20Xu=C3=A2n=20Sang?= Date: Thu, 10 Sep 2026 16:15:20 +0700 Subject: [PATCH] add 2265 solution --- .../description.md | 37 +++ .../solution.md | 267 ++++++++++++++++++ README.md | 5 +- SUMMARY.md | 1 + _sidebar.md | 1 + 5 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/description.md create mode 100644 Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/solution.md diff --git a/Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/description.md b/Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/description.md new file mode 100644 index 0000000..cbfcc31 --- /dev/null +++ b/Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/description.md @@ -0,0 +1,37 @@ +# 2265. Count Nodes Equal to Average of Subtree + +Given the `root` of a binary tree, return the number of nodes where the value of +the node is equal to the **average** of the values in its **subtree**. + +**Note:** + +- The **average** of `n` elements is the **sum** of the `n` elements divided by + `n` and **rounded down** to the nearest integer. +- A **subtree** of `root` is a tree consisting of `root` and all of its + descendants. + +## Example 1 + +```text +Input: root = [4,8,5,0,1,null,6] +Output: 5 +Explanation: +For the node with value 4: The average of its subtree is (4 + 8 + 5 + 0 + 1 + 6) / 6 = 24 / 6 = 4. +For the node with value 5: The average of its subtree is (5 + 6) / 2 = 11 / 2 = 5. +For the node with value 0: The average of its subtree is 0 / 1 = 0. +For the node with value 1: The average of its subtree is 1 / 1 = 1. +For the node with value 6: The average of its subtree is 6 / 1 = 6. +``` + +## Example 2 + +```text +Input: root = [1] +Output: 1 +Explanation: For the node with value 1: The average of its subtree is 1 / 1 = 1. +``` + +## Constraints + +- The number of nodes in the tree is in the range `[1, 1000]`. +- `0 <= Node.val <= 1000` diff --git a/Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/solution.md b/Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/solution.md new file mode 100644 index 0000000..2b9f35a --- /dev/null +++ b/Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/solution.md @@ -0,0 +1,267 @@ +# Intuition + +Testing one node means knowing two numbers about its subtree: the **sum** of the +values and the **count** of the nodes. Computing those separately for every node +would rescan overlapping subtrees, but they compose upward for free — a node's +totals are just its children's totals plus itself: + +$$\text{sum}(v) = \text{sum}(v_{left}) + \text{sum}(v_{right}) + val(v)$$ + +$$\text{count}(v) = \text{count}(v_{left}) + \text{count}(v_{right}) + 1$$ + +So a single post-order traversal answers every node at once. Each call returns the +pair to its parent and checks its own node on the way back up, which is why the +whole tree is visited exactly once. + +# Approach: Post-Order DFS Returning `(sum, count)` + +Define `dfs(node)` returning the pair for the subtree rooted at `node`: + +1. An empty subtree returns `(0, 0)` — the identity for both accumulators, so + leaves need no special case. +2. Recurse left and right to get their pairs. +3. Combine into this node's `sum` and `count` using the two identities above. +4. If `val == sum / count` (integer division), increment a counter that lives + outside the recursion. +5. Return `(sum, count)` to the parent. + +The order matters: the check happens **after** both recursive calls, because a +node's own totals are not known until its children have reported. That is what +makes this post-order rather than pre-order. + +## Why the counter is passed by reference + +Each call already returns `(sum, count)`, so the tally has to travel some other +way. All three versions keep it outside the return value: + +- **Python** stores it on the instance (`self.ans`), which the nested `dfs` + closes over. +- **Rust** threads `ans: &mut i32` through every call — the borrow checker allows + this because only one call holds the mutable reference at a time. +- **Go** passes `result *int` and does `*result++`. + +That last line is worth a note: `*result++` is valid Go and means `(*result)++`. +Go's `++` is a *statement* applying to the whole expression, so there is none of +C's `*p++` ambiguity between incrementing the pointer and the pointee. Verified to +compile and behave as intended. + +## Integer division is safe here, but only because values are non-negative + +The problem asks for the average **rounded down**, and the three languages spell +that differently: Python's `//` floors, while Rust and Go's `/` truncates toward +zero. Those disagree on negatives — Python gives `-7 // 2 == -4` where Go and Rust +give `-3`. + +It does not matter here: $$0 \le val \le 1000$$ means every subtree sum is +non-negative and every count is positive, so flooring and truncation coincide. If +the constraint ever admitted negative values the Python version would be the +correct one and the other two would need `div_euclid` or an explicit adjustment. + +## No overflow + +With at most `1000` nodes each at most `1000`, the largest possible subtree sum is +$$1000 \times 1000 = 10^6$$ — far inside a 32-bit integer, so Rust's `i32` needs +no widening. Confirmed by running the Rust version as a debug build, where an +arithmetic overflow aborts. + +## The recursion depth is exactly at CPython's limit + +The constraints allow a degenerate tree: 1000 nodes in a single spine, giving a +recursion depth of 1000. CPython's default recursion limit is also **1000**, and +the Python version raises `RecursionError: maximum recursion depth exceeded` on +that input when run with stock settings. Raising the limit to 10000 makes it +return correctly. + +LeetCode's judge raises the limit itself, so this passes there — but it is a real +edge of the constraint space rather than a theoretical one. Go grows its +goroutine stacks dynamically and Rust's 8 MB main-thread stack absorbs 1000 frames +comfortably; neither is affected. + +## Rust: the borrow held across recursion + +`let n = node.borrow();` keeps a `Ref` alive while `Self::dfs(&n.left, ..)` and +`Self::dfs(&n.right, ..)` run. That is fine because each child is a *different* +`RefCell`, so no cell is borrowed twice at once. It would panic at runtime on a +cyclic structure, which a binary tree never is. The debug build ran the whole test +corpus without a borrow panic. + +# Worked example + +`root = [4,8,5,0,1,null,6]`: + +```text + 4 + / \ + 8 5 + / \ \ + 0 1 6 +``` + +Post-order means children report before their parent: + +| visit order | node | subtree sum | count | `sum / count` | counts? | +| --- | --- | --- | --- | --- | --- | +| 1 | `0` | `0` | `1` | `0` | **yes** | +| 2 | `1` | `1` | `1` | `1` | **yes** | +| 3 | `8` | `9` | `3` | `9 / 3 = 3` | no | +| 4 | `6` | `6` | `1` | `6` | **yes** | +| 5 | `5` | `11` | `2` | `11 / 2 = 5` | **yes** | +| 6 | `4` | `24` | `6` | `24 / 6 = 4` | **yes** | + +Five nodes qualify. Two things this example demonstrates: every leaf always counts +(a single value equals its own average), and node `5` only qualifies because the +division floors — the exact average is `5.5`. + +For `root = [1]` the single node returns `(1, 1)` and `1 / 1 == 1`, so the answer +is `1`. + +# Complexity + +- Time complexity: $$O(n)$$, where `n` is the number of nodes — each is visited + once and does constant work. +- Space complexity: $$O(h)$$ for the recursion stack, where `h` is the tree + height. That is $$O(\log n)$$ when balanced and $$O(n)$$ for a degenerate spine, + which the constraints permit. + +Recomputing each subtree independently would be $$O(n \cdot h)$$ — up to +$$O(n^2)$$ on a spine. The returned pair is what collapses that to linear. + +# Code + +## Go + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func averageOfSubtree(root *TreeNode) int { + var dfs func (*TreeNode, *int) (int, int) + dfs = func(root *TreeNode, result *int) (int, int) { + if root == nil { + return 0, 0 + } + leftSum, leftCount := dfs(root.Left, result) + rightSum, rightCount := dfs(root.Right, result) + sum,count := leftSum + rightSum + root.Val, leftCount + rightCount + 1 + if root.Val == sum/count { + *result++ + } + return sum, count + } + ans := 0 + dfs(root, &ans) + return ans +} +``` + +The `var dfs func(...)` declaration before the assignment is required: a closure +cannot refer to itself inside a `:=` initialiser, because the name is not in scope +until that statement completes. + +## Rust + +```rust +// Definition for a binary tree node. +// #[derive(Debug, PartialEq, Eq)] +// pub struct TreeNode { +// pub val: i32, +// pub left: Option>>, +// pub right: Option>>, +// } +// +// impl TreeNode { +// #[inline] +// pub fn new(val: i32) -> Self { +// TreeNode { +// val, +// left: None, +// right: None +// } +// } +// } +use std::rc::Rc; +use std::cell::RefCell; +impl Solution { + pub fn dfs(root: &Option>>, ans: &mut i32) -> (i32, i32) { + match root { + Some(node) => { + let n = node.borrow(); + let (left_sum, left_count) = Self::dfs(&n.left, ans); + let (right_sum, right_count) = Self::dfs(&n.right, ans); + let (sum, count) = (left_sum + right_sum + n.val, left_count + right_count + 1); + if n.val == (sum / count) { + *ans += 1; + } + (sum, count) + }, + None => (0, 0) + } + } + + pub fn average_of_subtree(root: Option>>) -> i32 { + let mut ans = 0; + Self::dfs(&root, &mut ans); + ans + } +} +``` + +Taking `&Option>>` rather than the owned value means the +recursion borrows the tree instead of cloning `Rc` handles at every level, so the +reference counts are never touched during the traversal. + +## Python + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def averageOfSubtree(self, root: TreeNode) -> int: + self.ans = 0 + + def dfs(root: TreeNode) -> (int, int): + if root is None: + return 0, 0 + left_sum, left_count = dfs(root.left) + right_sum, right_count = dfs(root.right) + _sum, count = left_sum + right_sum + root.val, left_count + right_count + 1 + if root.val == _sum // count: + self.ans += 1 + + return _sum, count + + dfs(root) + return self.ans +``` + +`_sum` is spelled with a leading underscore to avoid shadowing the builtin `sum`, +which is a habit worth keeping in a function that might later want to call it. + +# Test cases + +| tree | answer | what it exercises | +| --- | --- | --- | +| `[4,8,5,0,1,null,6]` | `5` | Example 1 — traced above | +| `[1]` | `1` | Example 2 — single node | +| `[0]` | `1` | zero value, `0 / 1 == 0` | +| `[2,1,4]` | `3` | root `2` matches `7 / 3 = 2` by flooring | +| 1000-node left spine of `7`s | `1000` | maximum depth; every node averages `7` | +| complete tree of 1000 nodes | — | maximum size | + +All three implementations were checked against a brute force that, for every node, +walks that node's entire subtree from scratch and compares against the linear +version. The corpus was **4005** trees: the two examples, 4000 randomly shaped +trees of up to 40 nodes over value ranges `0..3`, `0..10` and `0..1000` (small +ranges make ties and near-misses common), a 1000-node complete tree, and a +1000-deep left spine. All three agreed on every tree, and the Rust build was made +in debug mode where an overflow or a double `RefCell` borrow would panic — neither +occurred. diff --git a/README.md b/README.md index 5f6824e..56f12bf 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Easy/350.Intersection-of-Two-Arrays-II/ ## Solutions index -Total: **216** problems with at least one solution file. +Total: **217** problems with at least one solution file. Solution links use variant names when multiple approaches or languages exist (`main` = `solution.md`, others = `solution-.md`). @@ -84,7 +84,7 @@ Solution links use variant names when multiple approaches or languages exist (`m | 3875. Construct Uniform Parity Array I | [Link](https://leetcode.com/problems/construct-uniform-parity-array-i/) | [main](Easy/3875.Construct-Uniform-Parity-Array-I/solution.md) | | 3903. Smallest Stable Index I | [Link](https://leetcode.com/problems/smallest-stable-index-i/) | [main](Easy/3903.Smallest-Stable-Index-I/solution.md) | -### Medium (124) +### Medium (125) | Problem | LeetCode | Solution | | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -173,6 +173,7 @@ Solution links use variant names when multiple approaches or languages exist (`m | 2192. All Ancestor of a Node in a Directed Acyclic Graph | [Link](https://leetcode.com/problems/all-ancestor-of-a-node-in-a-directed-acyclic-graph/) | [main](Medium/2192.All-Ancestor-of-a-Node-in-a-Directed-Acyclic-Graph/solution.md) | | 2196. Create Binary Tree From Descriptions | [Link](https://leetcode.com/problems/create-binary-tree-from-descriptions/) | [cpp](Medium/2196.Create-Binary-Tree-From-Descriptions/solution-cpp.md) · [go](Medium/2196.Create-Binary-Tree-From-Descriptions/solution-go.md) · [java](Medium/2196.Create-Binary-Tree-From-Descriptions/solution-java.md) · [rust](Medium/2196.Create-Binary-Tree-From-Descriptions/solution-rust.md) | | 2226. Maximum candies allocate to K children | [Link](https://leetcode.com/problems/maximum-candies-allocate-to-k-children/) | \[main]\(Medium/2226. Maximum-candies-allocate-to-K-children/solution.md) | +| 2265. Count Nodes Equal to Average of Subtree | [Link](https://leetcode.com/problems/count-nodes-equal-to-average-of-subtree/) | [main](Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/solution.md) | | 2270. Number Of Ways To Split Array | [Link](https://leetcode.com/problems/number-of-ways-to-split-array/) | [main](Medium/2270.Number-Of-Ways-To-Split-Array/solution.md) | | 2285. Maximum Total Importance of Roads | [Link](https://leetcode.com/problems/maximum-total-importance-of-roads/) | [main](Medium/2285.Maximum-Total-Importance-of-Roads/solution.md) | | 2326. Spiral Matrix IV | [Link](https://leetcode.com/problems/spiral-matrix-iv/) | [main](Medium/2326.Spiral-Matrix-IV/solution.md) | diff --git a/SUMMARY.md b/SUMMARY.md index 22e4c16..9cdaae0 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -164,6 +164,7 @@ * [java](Medium/2196.Create-Binary-Tree-From-Descriptions/solution-java.md) * [rust](Medium/2196.Create-Binary-Tree-From-Descriptions/solution-rust.md) * [2226. Maximum candies allocate to K children](Medium/2226.%20Maximum-candies-allocate-to-K-children/solution.md) +* [2265. Count Nodes Equal to Average of Subtree](Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/solution.md) * [2270. Number Of Ways To Split Array](Medium/2270.Number-Of-Ways-To-Split-Array/solution.md) * [2285. Maximum Total Importance of Roads](Medium/2285.Maximum-Total-Importance-of-Roads/solution.md) * [2326. Spiral Matrix IV](Medium/2326.Spiral-Matrix-IV/solution.md) diff --git a/_sidebar.md b/_sidebar.md index b9acedb..2df568a 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -159,6 +159,7 @@ - [java](Medium/2196.Create-Binary-Tree-From-Descriptions/solution-java.md) - [rust](Medium/2196.Create-Binary-Tree-From-Descriptions/solution-rust.md) - [2226. Maximum candies allocate to K children](Medium/2226.%20Maximum-candies-allocate-to-K-children/solution.md) + - [2265. Count Nodes Equal to Average of Subtree](Medium/2265.Count-Nodes-Equal-to-Average-of-Subtree/solution.md) - [2270. Number Of Ways To Split Array](Medium/2270.Number-Of-Ways-To-Split-Array/solution.md) - [2285. Maximum Total Importance of Roads](Medium/2285.Maximum-Total-Importance-of-Roads/solution.md) - [2326. Spiral Matrix IV](Medium/2326.Spiral-Matrix-IV/solution.md)