Using greedy without proof
Wrong move: Locally optimal choices may fail globally.
Usually fails on: Counterexamples appear on crafted input orderings.
Fix: Verify with exchange argument or monotonic objective before committing.
Move from brute-force thinking to an efficient approach using greedy strategy.
Given the root of a binary search tree, return a balanced binary search tree with the same node values. If there is more than one answer, return any of them.
A binary search tree is balanced if the depth of the two subtrees of every node never differs by more than 1.
Example 1:
Input: root = [1,null,2,null,3,null,4,null,null] Output: [2,1,3,null,null,null,4] Explanation: This is not the only correct answer, [3,1,4,null,2] is also correct.
Example 2:
Input: root = [2,1,3] Output: [2,1,3]
Constraints:
[1, 104].1 <= Node.val <= 105Problem summary: Given the root of a binary search tree, return a balanced binary search tree with the same node values. If there is more than one answer, return any of them. A binary search tree is balanced if the depth of the two subtrees of every node never differs by more than 1.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Greedy · Tree
[1,null,2,null,3,null,4]
[2,1,3]
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #1382: Balance a Binary Search Tree
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private List<Integer> nums = new ArrayList<>();
public TreeNode balanceBST(TreeNode root) {
dfs(root);
return build(0, nums.size() - 1);
}
private void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.left);
nums.add(root.val);
dfs(root.right);
}
private TreeNode build(int i, int j) {
if (i > j) {
return null;
}
int mid = (i + j) >> 1;
TreeNode left = build(i, mid - 1);
TreeNode right = build(mid + 1, j);
return new TreeNode(nums.get(mid), left, right);
}
}
// Accepted solution for LeetCode #1382: Balance a Binary Search Tree
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func balanceBST(root *TreeNode) *TreeNode {
ans := []int{}
var dfs func(*TreeNode)
dfs = func(root *TreeNode) {
if root == nil {
return
}
dfs(root.Left)
ans = append(ans, root.Val)
dfs(root.Right)
}
var build func(i, j int) *TreeNode
build = func(i, j int) *TreeNode {
if i > j {
return nil
}
mid := (i + j) >> 1
left := build(i, mid-1)
right := build(mid+1, j)
return &TreeNode{Val: ans[mid], Left: left, Right: right}
}
dfs(root)
return build(0, len(ans)-1)
}
# Accepted solution for LeetCode #1382: Balance a Binary Search Tree
# 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 balanceBST(self, root: TreeNode) -> TreeNode:
def dfs(root: TreeNode):
if root is None:
return
dfs(root.left)
nums.append(root.val)
dfs(root.right)
def build(i: int, j: int) -> TreeNode:
if i > j:
return None
mid = (i + j) >> 1
left = build(i, mid - 1)
right = build(mid + 1, j)
return TreeNode(nums[mid], left, right)
nums = []
dfs(root)
return build(0, len(nums) - 1)
// Accepted solution for LeetCode #1382: Balance a Binary Search Tree
struct Solution;
use rustgym_util::*;
trait Inorder {
fn inorder(self, nodes: &mut Vec<i32>);
fn build(nodes: &[i32]) -> TreeLink;
}
impl Inorder for TreeLink {
fn inorder(self, nodes: &mut Vec<i32>) {
if let Some(node) = self {
let mut node = node.borrow_mut();
let val = node.val;
let left = node.left.take();
let right = node.right.take();
left.inorder(nodes);
nodes.push(val);
right.inorder(nodes);
}
}
fn build(nodes: &[i32]) -> TreeLink {
let n = nodes.len();
if n == 0 {
None
} else {
let m = n / 2;
tree!(
nodes[m],
TreeLink::build(&nodes[..m]),
TreeLink::build(&nodes[m + 1..])
)
}
}
}
impl Solution {
fn balance_bst(root: TreeLink) -> TreeLink {
let mut nodes: Vec<i32> = vec![];
root.inorder(&mut nodes);
TreeLink::build(&nodes)
}
}
#[test]
fn test() {
let root = tree!(1, None, tree!(2, None, tree!(3, None, tree!(4))));
let res = tree!(3, tree!(2, tree!(1), None), tree!(4));
assert_eq!(Solution::balance_bst(root), res);
}
// Accepted solution for LeetCode #1382: Balance a Binary Search Tree
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function balanceBST(root: TreeNode | null): TreeNode | null {
const nums: number[] = [];
const dfs = (root: TreeNode | null): void => {
if (root == null) {
return;
}
dfs(root.left);
nums.push(root.val);
dfs(root.right);
};
const build = (i: number, j: number): TreeNode | null => {
if (i > j) {
return null;
}
const mid: number = (i + j) >> 1;
const left: TreeNode | null = build(i, mid - 1);
const right: TreeNode | null = build(mid + 1, j);
return new TreeNode(nums[mid], left, right);
};
dfs(root);
return build(0, nums.length - 1);
}
Use this to step through a reusable interview workflow for this problem.
Try every possible combination of choices. With n items each having two states (include/exclude), the search space is 2ⁿ. Evaluating each combination takes O(n), giving O(n × 2ⁿ). The recursion stack or subset storage uses O(n) space.
Greedy algorithms typically sort the input (O(n log n)) then make a single pass (O(n)). The sort dominates. If the input is already sorted or the greedy choice can be computed without sorting, time drops to O(n). Proving greedy correctness (exchange argument) is harder than the implementation.
Review these before coding to avoid predictable interview regressions.
Wrong move: Locally optimal choices may fail globally.
Usually fails on: Counterexamples appear on crafted input orderings.
Fix: Verify with exchange argument or monotonic objective before committing.
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.