Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions problems/1382-balance-a-binary-search-tree/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 1382. Balance a Binary Search Tree

[LeetCode Link](https://leetcode.com/problems/balance-a-binary-search-tree/)

Difficulty: Medium
Topics: Divide and Conquer, Greedy, Tree, Depth-First Search, Binary Search Tree, Binary Tree
Acceptance Rate: 84.9%

## Hints

### Hint 1

What property of a Binary Search Tree can you leverage to get all the values in sorted order? Once you have the values sorted, how might you build a balanced tree from them?

### Hint 2

An in-order traversal of a BST gives you the values in sorted order. Once you have a sorted array, think about how you would construct a balanced BST from it. Which element should be the root to ensure balance?

### Hint 3

The key insight is to use the middle element of a sorted array as the root of a balanced BST. This ensures equal (or near-equal) number of nodes in left and right subtrees. You can apply this recursively to build the entire tree.

## Approach

The solution follows a two-step approach:

**Step 1: In-order Traversal**
Perform an in-order traversal of the given BST to collect all node values in sorted order. Since in-order traversal of a BST visits nodes in ascending order, this gives us a sorted array of values.

**Step 2: Build Balanced BST from Sorted Array**
Use the sorted array to construct a balanced BST. The algorithm is:
1. Choose the middle element of the array (or subarray) as the root
2. Recursively build the left subtree from elements before the middle
3. Recursively build the right subtree from elements after the middle

This approach guarantees balance because at each step, we divide the remaining elements roughly equally between left and right subtrees. The height difference between any two subtrees will never exceed 1.

**Example walkthrough:**
For the skewed tree `[1,null,2,null,3,null,4]`:
1. In-order traversal gives: `[1, 2, 3, 4]`
2. Build balanced tree:
- Middle element is 2 (index 1) → root
- Left subarray `[1]` → middle is 1 → left child
- Right subarray `[3, 4]` → middle is 3 → right child
- Right subarray `[4]` → middle is 4 → right child of 3
3. Result: `[2,1,3,null,null,null,4]`

## Complexity Analysis

Time Complexity: O(n) where n is the number of nodes. We visit each node once during in-order traversal and once during tree construction.

Space Complexity: O(n) for storing the sorted array of node values. The recursion stack also uses O(log n) space for the balanced tree construction (or O(n) in worst case if we count the original skewed tree).

## Edge Cases

1. **Single node tree**: Tree is already balanced, but the algorithm handles it correctly by making it the root.
2. **Already balanced tree**: The algorithm will still reconstruct it, potentially in a different balanced configuration.
3. **Completely skewed tree**: The most common case this problem tests - ensures the algorithm properly balances a worst-case BST.
4. **Tree with duplicate values**: The problem constraints guarantee unique values (1 to 10^5), but the algorithm would handle duplicates correctly if they existed.
45 changes: 45 additions & 0 deletions problems/1382-balance-a-binary-search-tree/solution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

// Approach: Two-step process
// 1. Perform in-order traversal to extract sorted values from BST
// 2. Build balanced BST from sorted array by recursively choosing middle elements

type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}

func balanceBST(root *TreeNode) *TreeNode {
// Step 1: Collect values in sorted order via in-order traversal
values := []int{}
inorder(root, &values)

// Step 2: Build balanced BST from sorted array
return buildBalancedBST(values, 0, len(values)-1)
}

func inorder(node *TreeNode, values *[]int) {
if node == nil {
return
}
inorder(node.Left, values)
*values = append(*values, node.Val)
inorder(node.Right, values)
}

func buildBalancedBST(values []int, left, right int) *TreeNode {
if left > right {
return nil
}

// Choose middle element as root for balance
mid := left + (right-left)/2
node := &TreeNode{Val: values[mid]}

// Recursively build left and right subtrees
node.Left = buildBalancedBST(values, left, mid-1)
node.Right = buildBalancedBST(values, mid+1, right)

return node
}
191 changes: 191 additions & 0 deletions problems/1382-balance-a-binary-search-tree/solution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package main

import (
"math"
"testing"
)

func TestBalanceBST(t *testing.T) {
tests := []struct {
name string
input *TreeNode
}{
{
name: "example 1: skewed right tree",
input: buildTree([]interface{}{1, nil, 2, nil, 3, nil, 4}),
},
{
name: "example 2: already balanced tree",
input: buildTree([]interface{}{2, 1, 3}),
},
{
name: "edge case: single node",
input: &TreeNode{Val: 5},
},
{
name: "edge case: completely left-skewed tree",
input: buildSkewedLeft([]int{5, 4, 3, 2, 1}),
},
{
name: "edge case: larger unbalanced tree",
input: buildTree([]interface{}{1, nil, 2, nil, 3, nil, 4, nil, 5, nil, 6}),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := balanceBST(tt.input)

// Verify result is a valid BST
if !isBST(result, math.MinInt64, math.MaxInt64) {
t.Errorf("result is not a valid BST")
}

// Verify result is balanced
if !isBalanced(result) {
t.Errorf("result tree is not balanced")
}

// Verify same number of nodes
inputSize := countNodes(tt.input)
resultSize := countNodes(result)
if inputSize != resultSize {
t.Errorf("node count mismatch: input has %d nodes, result has %d nodes", inputSize, resultSize)
}

// Verify same values (via in-order traversal)
inputValues := inorderValues(tt.input)
resultValues := inorderValues(result)
if !equalSlices(inputValues, resultValues) {
t.Errorf("values mismatch: input has %v, result has %v", inputValues, resultValues)
}
})
}
}

// Helper: build tree from level-order array (nil represents missing nodes)
func buildTree(values []interface{}) *TreeNode {
if len(values) == 0 || values[0] == nil {
return nil
}

root := &TreeNode{Val: values[0].(int)}
queue := []*TreeNode{root}
i := 1

for len(queue) > 0 && i < len(values) {
node := queue[0]
queue = queue[1:]

// Left child
if i < len(values) && values[i] != nil {
node.Left = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Left)
}
i++

// Right child
if i < len(values) && values[i] != nil {
node.Right = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Right)
}
i++
}

return root
}

// Helper: build left-skewed tree from descending values
func buildSkewedLeft(values []int) *TreeNode {
if len(values) == 0 {
return nil
}
root := &TreeNode{Val: values[0]}
current := root
for i := 1; i < len(values); i++ {
current.Left = &TreeNode{Val: values[i]}
current = current.Left
}
return root
}

// Helper: check if tree is a valid BST
func isBST(node *TreeNode, min, max int) bool {
if node == nil {
return true
}
if node.Val <= min || node.Val >= max {
return false
}
return isBST(node.Left, min, node.Val) && isBST(node.Right, node.Val, max)
}

// Helper: check if tree is balanced
func isBalanced(node *TreeNode) bool {
_, balanced := checkBalance(node)
return balanced
}

func checkBalance(node *TreeNode) (int, bool) {
if node == nil {
return 0, true
}

leftHeight, leftBalanced := checkBalance(node.Left)
if !leftBalanced {
return 0, false
}

rightHeight, rightBalanced := checkBalance(node.Right)
if !rightBalanced {
return 0, false
}

if abs(leftHeight-rightHeight) > 1 {
return 0, false
}

return max(leftHeight, rightHeight) + 1, true
}

// Helper: count nodes in tree
func countNodes(node *TreeNode) int {
if node == nil {
return 0
}
return 1 + countNodes(node.Left) + countNodes(node.Right)
}

// Helper: get in-order values
func inorderValues(node *TreeNode) []int {
var values []int
inorder(node, &values)
return values
}

// Helper: compare two slices
func equalSlices(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

func abs(x int) int {
if x < 0 {
return -x
}
return x
}

func max(a, b int) int {
if a > b {
return a
}
return b
}
Loading