247 lines
11 KiB
YAML
247 lines
11 KiB
YAML
title: Convert BST to Greater Tree
|
|
slug: convert-bst-to-greater-tree
|
|
difficulty: medium
|
|
leetcode_id: 538
|
|
leetcode_url: https://leetcode.com/problems/convert-bst-to-greater-tree/
|
|
categories:
|
|
- trees
|
|
patterns:
|
|
- dfs
|
|
- tree-traversal
|
|
|
|
function_signature: "def convert_bst(root: TreeNode | None) -> TreeNode | None:"
|
|
|
|
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: [] }
|
|
expected: []
|
|
- input: { root: [5] }
|
|
expected: [5]
|
|
- input: { root: [2, 1, 3] }
|
|
expected: [5, 6, 3]
|
|
- input: { root: [5, 3, 8, 1, 4, 6, 9] }
|
|
expected: [33, 37, 17, 38, 34, 23, 9]
|
|
- input: { root: [1, null, 2, null, 3, null, 4] }
|
|
expected: [10, null, 9, null, 7, null, 4]
|
|
- input: { root: [-3, -4, -2] }
|
|
expected: [-5, -9, -2]
|
|
|
|
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: |
|
|
- `0 <= number of nodes <= 10^4`
|
|
- `-10^4 <= Node.val <= 10^4`
|
|
- All the values in the tree are **unique**
|
|
- `root` is guaranteed to be a valid binary search tree
|
|
|
|
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 new value equals its original value plus the sum of 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 stays 1 (no greater values). Node 0 becomes 0 + 1 = 1."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you have a sorted list of numbers and you want to replace each number with the sum of itself and all numbers greater than it. You would naturally start from the **largest number** and work backwards, keeping a running total.
|
|
|
|
The key insight is that a BST has a built-in sorted order: an **in-order traversal** (left → node → right) visits nodes in ascending order. But we need descending order to accumulate sums from largest to smallest.
|
|
|
|
The solution is beautifully simple: use a **reverse in-order traversal** (right → node → left). This visits nodes from largest to smallest. As we traverse, we maintain a running sum of all values seen so far. Each node's new value becomes this running sum (which includes its original value plus all greater values).
|
|
|
|
Think of it like this: you're walking through the tree in reverse sorted order, carrying a "cumulative total" that grows with each node you visit. When you reach a node, you add its value to your total, then update the node with this new total.
|
|
|
|
approach: |
|
|
We solve this using **Reverse In-Order Traversal** with a running sum:
|
|
|
|
**Step 1: Understand the traversal order**
|
|
|
|
- Standard in-order (left → node → right) gives ascending order
|
|
- Reverse in-order (right → node → left) gives **descending order**
|
|
- We need descending order to accumulate sums correctly
|
|
|
|
|
|
|
|
**Step 2: Initialise a running sum variable**
|
|
|
|
- `running_sum`: Set to `0` initially
|
|
- This tracks the sum of all nodes visited so far (all values greater than current)
|
|
|
|
|
|
|
|
**Step 3: Perform reverse in-order traversal**
|
|
|
|
- Recursively visit the **right subtree** first (larger values)
|
|
- Process the **current node**: add its value to `running_sum`, then update the node's value to `running_sum`
|
|
- Recursively visit the **left subtree** (smaller values)
|
|
|
|
|
|
|
|
**Step 4: Return the modified root**
|
|
|
|
- The tree is modified in-place during traversal
|
|
- Return the original root reference
|
|
|
|
common_pitfalls:
|
|
- title: Using Standard In-Order Traversal
|
|
description: |
|
|
A common mistake is using left → node → right traversal. This visits nodes in ascending order, but we need descending order to know the sum of greater values before processing each node.
|
|
|
|
With ascending order, when you visit a node, you don't yet know the sum of all greater values because you haven't visited them yet!
|
|
wrong_approach: "Standard in-order traversal (left → node → right)"
|
|
correct_approach: "Reverse in-order traversal (right → node → left)"
|
|
|
|
- title: Two-Pass Solution
|
|
description: |
|
|
Some might think they need two passes: first to calculate the total sum, then another to update each node. While this works, it's unnecessarily complex.
|
|
|
|
The reverse in-order traversal elegantly solves this in a single pass because we naturally visit larger values first.
|
|
wrong_approach: "First pass to sum all values, second pass to update nodes"
|
|
correct_approach: "Single reverse in-order pass with running sum"
|
|
|
|
- title: Forgetting to Update Running Sum Before Node Value
|
|
description: |
|
|
The order of operations matters. You must add the current node's value to the running sum **before** updating the node's value, otherwise you lose the original value.
|
|
|
|
```python
|
|
# Wrong order:
|
|
node.val = running_sum # Lost original value!
|
|
running_sum += node.val # Now adding the wrong value
|
|
|
|
# Correct order:
|
|
running_sum += node.val # Add original value first
|
|
node.val = running_sum # Then update node
|
|
```
|
|
wrong_approach: "Update node value before adding to running sum"
|
|
correct_approach: "Add to running sum first, then update node value"
|
|
|
|
key_takeaways:
|
|
- "**Reverse in-order traversal** visits BST nodes in descending order — essential when you need to process from largest to smallest"
|
|
- "**Running accumulator pattern**: maintain a running total during traversal when nodes depend on previously visited values"
|
|
- "**BST property exploitation**: the sorted nature of BSTs enables elegant single-pass solutions"
|
|
- "This problem is identical to LeetCode 1038 (Binary Search Tree to Greater Sum Tree)"
|
|
|
|
time_complexity: "O(n). Each node is visited exactly once during the traversal."
|
|
space_complexity: "O(h) where h is the height of the tree. This is the recursion stack space — O(log n) for a balanced tree, O(n) for a skewed tree."
|
|
|
|
solutions:
|
|
- approach_name: Reverse In-Order 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 convert_bst(root: TreeNode | None) -> TreeNode | None:
|
|
# Running sum of all values greater than current node
|
|
running_sum = 0
|
|
|
|
def reverse_inorder(node: TreeNode | None) -> None:
|
|
nonlocal running_sum
|
|
if not node:
|
|
return
|
|
|
|
# Visit right subtree first (larger values)
|
|
reverse_inorder(node.right)
|
|
|
|
# Process current node: add to 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 visited once.
|
|
|
|
**Space Complexity:** O(h) — Recursion stack depth equals tree height.
|
|
|
|
The recursive approach directly implements reverse in-order traversal. We use `nonlocal` to maintain the running sum across recursive calls. The tree is modified in-place.
|
|
|
|
- approach_name: Reverse In-Order Traversal (Iterative)
|
|
is_optimal: true
|
|
code: |
|
|
def convert_bst(root: TreeNode | None) -> TreeNode | None:
|
|
running_sum = 0
|
|
stack = []
|
|
current = root
|
|
|
|
# Iterative reverse in-order: right -> node -> left
|
|
while stack or current:
|
|
# Go as far right as possible
|
|
while current:
|
|
stack.append(current)
|
|
current = current.right
|
|
|
|
# Process the rightmost unprocessed 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 visited once.
|
|
|
|
**Space Complexity:** O(h) — Stack stores at most h nodes.
|
|
|
|
The iterative version uses an explicit stack to simulate recursion. We traverse right as far as possible, then process nodes and move left. This avoids recursion overhead and stack overflow for very deep trees.
|
|
|
|
- approach_name: Morris Traversal
|
|
is_optimal: true
|
|
code: |
|
|
def convert_bst(root: TreeNode | None) -> TreeNode | None:
|
|
running_sum = 0
|
|
current = root
|
|
|
|
while current:
|
|
if not current.right:
|
|
# No right child: process node, move left
|
|
running_sum += current.val
|
|
current.val = running_sum
|
|
current = current.left
|
|
else:
|
|
# Find in-order predecessor in right subtree
|
|
# (leftmost node of right subtree)
|
|
predecessor = current.right
|
|
while predecessor.left and predecessor.left != current:
|
|
predecessor = predecessor.left
|
|
|
|
if not predecessor.left:
|
|
# Create temporary link back to current
|
|
predecessor.left = current
|
|
current = current.right
|
|
else:
|
|
# Remove temporary link, process current node
|
|
predecessor.left = None
|
|
running_sum += current.val
|
|
current.val = running_sum
|
|
current = current.left
|
|
|
|
return root
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Each edge traversed at most twice.
|
|
|
|
**Space Complexity:** O(1) — No additional space beyond pointers.
|
|
|
|
Morris traversal achieves O(1) space by temporarily modifying tree pointers to create "threads" back to ancestors. This is the most space-efficient solution but modifies and restores the tree structure during traversal.
|