Files
codetutor/backend/data/questions/binary-search-tree-to-greater-sum-tree.yaml

244 lines
10 KiB
YAML

title: Binary Search Tree to Greater Sum Tree
slug: binary-search-tree-to-greater-sum-tree
difficulty: medium
leetcode_id: 1038
leetcode_url: https://leetcode.com/problems/binary-search-tree-to-greater-sum-tree/
categories:
- trees
patterns:
- dfs
- tree-traversal
function_signature: "def bst_to_gst(root: TreeNode) -> TreeNode:"
test_cases:
visible:
- input: { root: [4, 1, 6, 0, 2, 5, 7, null, null, null, 3, null, null, null, 8] }
expected: [30, 36, 21, 36, 35, 26, 15, null, null, null, 33, null, null, null, 8]
- input: { root: [0, null, 1] }
expected: [1, null, 1]
hidden:
- input: { root: [1] }
expected: [1]
- input: { root: [2, 1, 3] }
expected: [5, 6, 3]
- input: { root: [3, 2, 4, 1] }
expected: [7, 9, 4, 10]
- input: { root: [5, 3, 7, 2, 4, 6, 8] }
expected: [26, 33, 15, 35, 30, 21, 8]
description: |
Given the `root` of a Binary Search Tree (BST), convert it to a Greater Tree such that every key of the original BST is changed to the original key plus the sum of all keys greater than the original key in BST.
As a reminder, a *binary search tree* is a tree that satisfies these constraints:
- The left subtree of a node contains only nodes with keys **less than** the node's key.
- The right subtree of a node contains only nodes with keys **greater than** the node's key.
- Both the left and right subtrees must also be binary search trees.
constraints: |
- `1 <= number of nodes <= 100`
- `0 <= Node.val <= 100`
- All values in the tree are **unique**
examples:
- input: "root = [4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]"
output: "[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]"
explanation: "Each node's value becomes the sum of itself plus all nodes with greater values. For example, node 4 becomes 4+5+6+7+8=30."
- input: "root = [0,null,1]"
output: "[1,null,1]"
explanation: "Node 1 has no greater values so stays 1. Node 0 becomes 0+1=1."
explanation:
intuition: |
The key insight is understanding what "all keys greater than the original key" means in a BST.
In a BST, an **inorder traversal** (left → node → right) visits nodes in ascending order. If we **reverse** this traversal order (right → node → left), we visit nodes in **descending order** — from largest to smallest.
Think of it like this: imagine you have a sorted list of all node values `[0, 1, 2, 3, 4, 5, 6, 7, 8]`. For each value, you need to add all values to its right. If you traverse from right to left, you can maintain a **running sum** that accumulates as you go.
When you visit node with value 8 (the largest), no values are greater, so it stays 8. When you visit 7, you add the running sum (8), making it 15. When you visit 6, you add the running sum (8+7=15), making it 21. And so on.
By traversing in reverse-inorder (right → node → left), each node we visit is smaller than all previously visited nodes, so our running sum naturally represents "the sum of all greater values."
approach: |
We use a **Reverse Inorder Traversal** with a running sum:
**Step 1: Understand the traversal order**
- Normal inorder: left → node → right (ascending order)
- Reverse inorder: right → node → left (descending order)
- By visiting nodes from largest to smallest, we can build up the sum incrementally
&nbsp;
**Step 2: Maintain a running sum**
- `running_sum`: Tracks the sum of all nodes visited so far
- Since we visit in descending order, this equals "sum of all greater values"
- Initialise to `0` before traversal
&nbsp;
**Step 3: Perform reverse inorder traversal**
- Recursively visit the right subtree first (larger values)
- At the current node:
- Add the node's value to `running_sum`
- Update the node's value to `running_sum`
- Recursively visit the left subtree (smaller values)
&nbsp;
**Step 4: Return the modified root**
- The tree is modified in-place
- Return the original root reference
&nbsp;
This approach processes each node exactly once, and the running sum at each node naturally represents the sum of all greater values plus the current value.
common_pitfalls:
- title: Using Normal Inorder Instead of Reverse
description: |
A common mistake is performing a normal inorder traversal (left → node → right). This visits nodes in ascending order, which means when you reach a node, you've only seen smaller values — not the greater ones you need to sum.
With reverse inorder (right → node → left), you visit larger values first, so your running sum correctly represents "sum of all greater values."
wrong_approach: "Normal inorder traversal (left → node → right)"
correct_approach: "Reverse inorder traversal (right → node → left)"
- title: Forgetting to Update Node Value Before Recursing Left
description: |
The order of operations matters. You must:
1. Visit right subtree
2. Add current value to running sum
3. Update current node's value
4. Visit left subtree
If you update the node after visiting the left subtree, nodes in the left subtree won't include this node's contribution in their sums.
- title: Not Modifying In-Place
description: |
The problem asks you to modify the tree in-place and return the same root. Don't create a new tree or return a different root node.
Simply update `node.val = running_sum` at each node during traversal.
key_takeaways:
- "**Reverse inorder traversal** visits BST nodes in descending order — useful when you need to process from largest to smallest"
- "**Running sum pattern**: When accumulating values during traversal, consider which direction gives you the values you need"
- "**BST property exploitation**: The structure of a BST provides natural ordering that can be leveraged for efficient solutions"
- "This problem is identical to LeetCode 538 (Convert BST to Greater Tree) — recognising equivalent problems is a valuable skill"
time_complexity: "O(n). We visit each node exactly once during the reverse inorder traversal."
space_complexity: "O(h) where h is the height of the tree. This is the recursion stack depth, which is O(log n) for a balanced tree or O(n) for a skewed tree."
solutions:
- approach_name: Reverse Inorder Traversal (Recursive)
is_optimal: true
code: |
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def bst_to_gst(root: TreeNode) -> TreeNode:
# Running sum of all nodes visited so far (larger values)
running_sum = 0
def reverse_inorder(node: TreeNode) -> None:
nonlocal running_sum
if not node:
return
# Visit right subtree first (larger values)
reverse_inorder(node.right)
# Add current value to running sum, then update node
running_sum += node.val
node.val = running_sum
# Visit left subtree (smaller values)
reverse_inorder(node.left)
reverse_inorder(root)
return root
explanation: |
**Time Complexity:** O(n) — Each node is visited exactly once.
**Space Complexity:** O(h) — Recursion stack depth equals tree height.
We traverse the tree in reverse inorder (right → node → left), maintaining a running sum. At each node, we add its value to the sum and update the node to hold this accumulated value. Since we visit larger nodes first, the running sum always represents the sum of all greater values.
- approach_name: Iterative with Stack
is_optimal: true
code: |
def bst_to_gst(root: TreeNode) -> TreeNode:
running_sum = 0
stack = []
current = root
# Iterative reverse inorder: right -> node -> left
while stack or current:
# Go as far right as possible
while current:
stack.append(current)
current = current.right
# Process current node
current = stack.pop()
running_sum += current.val
current.val = running_sum
# Move to left subtree
current = current.left
return root
explanation: |
**Time Complexity:** O(n) — Each node is visited exactly once.
**Space Complexity:** O(h) — Stack stores at most h nodes (tree height).
This is the iterative version using an explicit stack. We simulate the recursion by first pushing all right children onto the stack, processing nodes as we pop them, then moving to left children. This avoids recursion overhead but has the same time and space complexity.
- approach_name: Morris Traversal
is_optimal: true
code: |
def bst_to_gst(root: TreeNode) -> TreeNode:
running_sum = 0
current = root
while current:
if not current.right:
# No right subtree: process node, move left
running_sum += current.val
current.val = running_sum
current = current.left
else:
# Find inorder predecessor in right subtree
# (leftmost node in right subtree)
successor = current.right
while successor.left and successor.left != current:
successor = successor.left
if not successor.left:
# Create temporary link back to current
successor.left = current
current = current.right
else:
# Remove temporary link, process node
successor.left = None
running_sum += current.val
current.val = running_sum
current = current.left
return root
explanation: |
**Time Complexity:** O(n) — Each edge is traversed at most twice.
**Space Complexity:** O(1) — No recursion stack or explicit stack used.
Morris traversal achieves O(1) space by temporarily modifying the tree structure. We create temporary links from nodes back to their "successors" in the reverse inorder. This is an advanced technique that trades code complexity for space efficiency.