Binary Search Trees
A binary tree kept in order, so search, insert and delete follow one path down: O(log n) when balanced, O(n) when it degenerates.
How It Works
A binary search tree (BST) is a binary tree with one rule at every node: every value in its left subtree is smaller than the node, and every value in its right subtree is larger. The rule covers the whole subtree, not just the two children, and it's what makes the tree searchable.
8 search for 7
/ \ 7 < 8 go left
3 10 7 > 3 go right
/ \ \ 7 > 6 go right
1 6 14 7 = 7 found
/ \ /
4 7 13Searching is binary search on a tree. Compare the target with the current node: smaller means go left, larger means go right, and the other subtree is ruled out for good. Insert and delete follow the same single path down, so all three cost O(h), where h is the height of the tree.
Search and Insert Follow One Path
Step 1 of 10
root
- Node being compared
- Path so far
- Ruled out
- Found or inserted
Search for 7, starting at the root. Each comparison sends you left or right and rules out the other side for good.
All 10 steps as text
- Search for
7, starting at the root. Each comparison sends you left or right and rules out the other side for good. 7 < 8: if7is anywhere, it's in the left subtree, so10, 13, 14are ruled out. Go left.7 > 3: if7is anywhere, it's in the right subtree, so1is ruled out. Go right.7 > 6: if7is anywhere, it's in the right subtree, so4is ruled out. Go right.7 = 7: found, andsearchreturnstrue. That took 4 comparisons, one per level, while the other 5 values were ruled out without a look.- Now insert
2. It walks the same path a search for2would, and the new node goes exactly where that search falls off the tree. 2 < 8, so2belongs in the left subtree. Go left.2 < 3, so2belongs in the left subtree. Go left.2 > 1, so go right. But1has no right child: a search for2falls off the tree here, and that empty spot is where2belongs.insertmakes a new node for2and returns it, and1reattaches it withnode.right = insert(node.right, 2). Every node on the path still has2on its correct side.
So everything depends on the height. A balanced tree with n nodes has a height around log n, which makes each operation O(log n): about 20 steps for a million values. But a plain BST takes whatever shape the insertion order gives it. Insert values that are already sorted and each one becomes the right child of the one before. The tree degenerates into a linked list with height n - 1, and every operation becomes O(n).
balanced, height 2 degenerate, height 6
(insert 4 2 6 1 3 5 7) (insert 1 2 3 4 5 6 7)
4 1
/ \ \
2 6 2
/ \ / \ \
1 3 5 7 3
\
4
\
5
\
6
\
7When to Reach for It
- The input is a BST, so you can steer left or right by comparing values instead of searching everything.
- You need a tree's values in sorted order, or its kth smallest or largest value.
- You need a floor, ceiling, predecessor or successor: the nearest value below or above a target.
- Values are added and removed over time, and you still need them in order, as with an ordered set or map.
Complexity
| Operation | Time | Space |
|---|---|---|
| Search, insert or delete (balanced) | O(log n) | O(1) iterative, O(log n) recursive |
| Search, insert or delete (degenerate) | O(n) | O(1) iterative, O(n) recursive |
| In-order walk (sorted output) | O(n) | O(h) stack |
| Kth smallest, stopping early | O(h + k) | O(h) |
The Template
Search, Insert and Walk a BST
search walks down from the root, going left when the target is smaller and right when it's larger. It stops when it finds the value or falls off the bottom of the tree. It needs no recursion and no extra space.
insert follows the same path. The spot where a search for the new value falls off the tree is exactly where that value belongs, so a new node goes there. Each call returns its subtree's root, which lets the parent reattach it (node.left = insert(node.left, val)) without tracking parent pointers. This version ignores a value that's already present. Whatever rule you pick for duplicates, search and insert must agree on it.
inorder visits the left subtree, then the node, then the right subtree. At every node, everything on the left is smaller and everything on the right is larger, so the values come out sorted.
It keeps its own stack instead of recursing, the same loop as inorderIterative in Binary Trees. Go left as far as you can, pushing each node, because the smallest value not yet visited is down there. Pop one and record it, then walk its right subtree, which holds the next values in order. The walk's whole state is stack and node, so you can stop it after k values or pause it between any two. Kth Smallest below stops early, and Binary Search Tree Iterator in the practice list pauses.
Deletion isn't in the code, but its three cases come up often. First find the node, the same way search does. Then:
- No children: remove the node.
- One child: replace the node with its only child. Everything under that child already sits on the correct side of the node's parent.
- Two children: copy in the node's in-order successor, the smallest value in its right subtree (step right once, then left as far as you can). Then delete the successor from the right subtree. The successor has no left child, so that second delete is always one of the first two cases.
Time O(h) search and insert, O(n) walkSpace O(1) search, O(h) insert and walk
class TreeNode:
def __init__(self, val: int) -> None:
self.val = val
self.left: TreeNode | None = None
self.right: TreeNode | None = None
def search(root: TreeNode | None, target: int) -> bool:
"""Whether `target` is in the BST rooted at `root`."""
node = root
while node is not None:
if target == node.val:
return True
# Smaller values live on the left and larger ones on the right, so one
# comparison rules out a whole subtree.
node = node.left if target < node.val else node.right
return False
def insert(node: TreeNode | None, val: int) -> TreeNode:
"""Adds `val` to the BST rooted at `node` and returns the subtree's root, so
the caller can reattach it. A value already in the tree is left alone."""
# A search for val would fall off the tree here, so this is where it goes.
if node is None:
return TreeNode(val)
if val < node.val:
node.left = insert(node.left, val)
elif val > node.val:
node.right = insert(node.right, val)
return node
def inorder(root: TreeNode | None) -> list[int]:
"""The values in sorted order. The walk keeps its own stack, so it could
stop or pause between any two values."""
result: list[int] = []
stack: list[TreeNode] = []
node = root
while node is not None or stack:
# The smallest value not yet visited is as far left as you can go.
while node is not None:
stack.append(node)
node = node.left
node = stack.pop()
result.append(node.val)
# Everything in its right subtree comes next in sorted order.
node = node.right
return result
if __name__ == "__main__":
root = None
for val in [8, 3, 10, 1, 6, 14, 4, 7, 13]:
root = insert(root, val)
print(inorder(root)) # [1, 3, 4, 6, 7, 8, 10, 13, 14]
print(search(root, 7)) # True
print(search(root, 5)) # False
print(search(None, 8)) # Falseclass TreeNode {
val: number;
left: TreeNode | null = null;
right: TreeNode | null = null;
constructor(val: number) {
this.val = val;
}
}
// Whether `target` is in the BST rooted at `root`.
export function search(root: TreeNode | null, target: number): boolean {
let node = root;
while (node !== null) {
if (target === node.val) return true;
// Smaller values live on the left and larger ones on the right, so one
// comparison rules out a whole subtree.
node = target < node.val ? node.left : node.right;
}
return false;
}
// Adds `val` to the BST rooted at `node` and returns the subtree's root, so
// the caller can reattach it. A value already in the tree is left alone.
export function insert(node: TreeNode | null, val: number): TreeNode {
// A search for val would fall off the tree here, so this is where it goes.
if (node === null) return new TreeNode(val);
if (val < node.val) node.left = insert(node.left, val);
else if (val > node.val) node.right = insert(node.right, val);
return node;
}
// The values in sorted order. The walk keeps its own stack, so it could stop
// or pause between any two values.
export function inorder(root: TreeNode | null): number[] {
const result: number[] = [];
const stack: TreeNode[] = [];
let node = root;
while (node !== null || stack.length > 0) {
// The smallest value not yet visited is as far left as you can go.
while (node !== null) {
stack.push(node);
node = node.left;
}
node = stack.pop()!;
result.push(node.val);
// Everything in its right subtree comes next in sorted order.
node = node.right;
}
return result;
}
let root: TreeNode | null = null;
for (const val of [8, 3, 10, 1, 6, 14, 4, 7, 13]) root = insert(root, val);
console.log(JSON.stringify(inorder(root))); // [1,3,4,6,7,8,10,13,14]
console.log(search(root, 7)); // true
console.log(search(root, 5)); // false
console.log(search(null, 8)); // falsepackage main
import "fmt"
// TreeNode is one node of a binary search tree.
type TreeNode struct {
Val int
Left, Right *TreeNode
}
// search reports whether target is in the BST rooted at root.
func search(root *TreeNode, target int) bool {
node := root
for node != nil {
if target == node.Val {
return true
}
// Smaller values live on the left and larger ones on the right, so one
// comparison rules out a whole subtree.
if target < node.Val {
node = node.Left
} else {
node = node.Right
}
}
return false
}
// insert adds val to the BST rooted at node and returns the subtree's root, so
// the caller can reattach it. A value already in the tree is left alone.
func insert(node *TreeNode, val int) *TreeNode {
// A search for val would fall off the tree here, so this is where it goes.
if node == nil {
return &TreeNode{Val: val}
}
if val < node.Val {
node.Left = insert(node.Left, val)
} else if val > node.Val {
node.Right = insert(node.Right, val)
}
return node
}
// inorder returns the values in sorted order. The walk keeps its own stack, so
// it could stop or pause between any two values.
func inorder(root *TreeNode) []int {
result := []int{}
stack := []*TreeNode{}
node := root
for node != nil || len(stack) > 0 {
// The smallest value not yet visited is as far left as you can go.
for node != nil {
stack = append(stack, node)
node = node.Left
}
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, node.Val)
// Everything in its right subtree comes next in sorted order.
node = node.Right
}
return result
}
func main() {
var root *TreeNode
for _, val := range []int{8, 3, 10, 1, 6, 14, 4, 7, 13} {
root = insert(root, val)
}
fmt.Println(inorder(root)) // [1 3 4 6 7 8 10 13 14]
fmt.Println(search(root, 7)) // true
fmt.Println(search(root, 5)) // false
fmt.Println(search(nil, 8)) // false
}using System;
using System.Collections.Generic;
public class Solution
{
public class TreeNode
{
public int Val;
public TreeNode? Left, Right;
public TreeNode(int val) => Val = val;
}
// Whether target is in the BST rooted at root.
public static bool Search(TreeNode? root, int target)
{
var node = root;
while (node != null)
{
if (target == node.Val) return true;
// Smaller values live on the left and larger ones on the right, so one
// comparison rules out a whole subtree.
node = target < node.Val ? node.Left : node.Right;
}
return false;
}
// Adds val to the BST rooted at node and returns the subtree's root, so
// the caller can reattach it. A value already in the tree is left alone.
public static TreeNode Insert(TreeNode? node, int val)
{
// A search for val would fall off the tree here, so this is where it goes.
if (node == null) return new TreeNode(val);
if (val < node.Val) node.Left = Insert(node.Left, val);
else if (val > node.Val) node.Right = Insert(node.Right, val);
return node;
}
// The values in sorted order. The walk keeps its own stack, so it could stop
// or pause between any two values.
public static List<int> Inorder(TreeNode? root)
{
var result = new List<int>();
var stack = new Stack<TreeNode>();
var node = root;
while (node != null || stack.Count > 0)
{
// The smallest value not yet visited is as far left as you can go.
while (node != null)
{
stack.Push(node);
node = node.Left;
}
node = stack.Pop();
result.Add(node.Val);
// Everything in its right subtree comes next in sorted order.
node = node.Right;
}
return result;
}
public static void Main()
{
TreeNode? root = null;
foreach (int val in new[] { 8, 3, 10, 1, 6, 14, 4, 7, 13 }) root = Insert(root, val);
Console.WriteLine("[" + string.Join(", ", Inorder(root)) + "]"); // [1, 3, 4, 6, 7, 8, 10, 13, 14]
Console.WriteLine(Search(root, 7)); // True
Console.WriteLine(Search(root, 5)); // False
Console.WriteLine(Search(null, 8)); // False
}
}import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
public class Solution {
static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
// Whether target is in the BST rooted at root.
static boolean search(TreeNode root, int target) {
TreeNode node = root;
while (node != null) {
if (target == node.val) return true;
// Smaller values live on the left and larger ones on the right, so one
// comparison rules out a whole subtree.
node = target < node.val ? node.left : node.right;
}
return false;
}
// Adds val to the BST rooted at node and returns the subtree's root, so
// the caller can reattach it. A value already in the tree is left alone.
static TreeNode insert(TreeNode node, int val) {
// A search for val would fall off the tree here, so this is where it goes.
if (node == null) return new TreeNode(val);
if (val < node.val) node.left = insert(node.left, val);
else if (val > node.val) node.right = insert(node.right, val);
return node;
}
// The values in sorted order. The walk keeps its own stack, so it could stop
// or pause between any two values.
static List<Integer> inorder(TreeNode root) {
List<Integer> result = new ArrayList<>();
ArrayDeque<TreeNode> stack = new ArrayDeque<>();
TreeNode node = root;
while (node != null || !stack.isEmpty()) {
// The smallest value not yet visited is as far left as you can go.
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
result.add(node.val);
// Everything in its right subtree comes next in sorted order.
node = node.right;
}
return result;
}
public static void main(String[] args) {
TreeNode root = null;
for (int val : new int[] {8, 3, 10, 1, 6, 14, 4, 7, 13}) root = insert(root, val);
System.out.println(inorder(root)); // [1, 3, 4, 6, 7, 8, 10, 13, 14]
System.out.println(search(root, 7)); // true
System.out.println(search(root, 5)); // false
System.out.println(search(null, 8)); // false
}
}Worked Problems
Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.
Problem
Decide whether a binary tree is a valid binary search tree. In a valid one, every value in a node's left subtree is strictly smaller than the node, every value in its right subtree is strictly larger, and the same holds at every node.
Example: [2, 1, 3] is valid. [5, 1, 4, null, null, 3, 6] is not, because 4 is the right child of 5 but smaller than it.
Show SolutionHide Solution
Approach
The tempting check compares each node only with its two children, and it's wrong. In the tree below, every parent and child pair looks fine, but 3 sits in the right subtree of 5, where every value must be larger than 5.
5 node must be in ok?
/ \ 5 (-inf, +inf) yes
4 6 4 (-inf, 5) yes
/ \ 6 (5, +inf) yes
3 7 3 (5, 6) no, 3 < 5
7 (6, +inf) yesSo carry the limits down. Each node must lie strictly inside an interval (low, high) set by its ancestors. Stepping left, the node's value becomes the new upper bound. Stepping right, it becomes the new lower bound. The root starts with no bounds at all, written as null, which also avoids any trouble with nodes that hold the smallest or largest integer.
Time O(n)Space O(h)
from collections import deque
class TreeNode:
def __init__(self, val: int) -> None:
self.val = val
self.left: TreeNode | None = None
self.right: TreeNode | None = None
def build_tree(values: list[int | None]) -> TreeNode | None:
"""Builds a tree from level-order values, where None marks a missing child.
This is how LeetCode writes trees down."""
first = values[0] if values else None
if first is None:
return None
root = TreeNode(first)
queue = deque([root])
i = 1
# Hand out the remaining values two at a time, left child then right, to
# the nodes in the order they were created.
while i < len(values):
node = queue.popleft()
left = values[i]
if left is not None:
node.left = TreeNode(left)
queue.append(node.left)
i += 1
right = values[i] if i < len(values) else None
if right is not None:
node.right = TreeNode(right)
queue.append(node.right)
i += 1
return root
def is_valid(node: TreeNode | None, low: int | None, high: int | None) -> bool:
"""Whether every value in the subtree at `node` lies strictly between `low`
and `high` (None means no bound), with the same rule holding all the way down."""
if node is None:
return True
if (low is not None and node.val <= low) or (high is not None and node.val >= high):
return False
# The left subtree must also stay below this node, and the right one above it.
return is_valid(node.left, low, node.val) and is_valid(node.right, node.val, high)
def is_valid_bst(root: TreeNode | None) -> bool:
"""Whether the tree is a binary search tree: at every node, the whole left
subtree is smaller and the whole right subtree is larger."""
return is_valid(root, None, None)
if __name__ == "__main__":
print(is_valid_bst(build_tree([2, 1, 3]))) # True
print(is_valid_bst(build_tree([5, 1, 4, None, None, 3, 6]))) # False
print(is_valid_bst(build_tree([5, 4, 6, None, None, 3, 7]))) # False
print(is_valid_bst(build_tree([2, 2, 2]))) # Falseclass TreeNode {
val: number;
left: TreeNode | null = null;
right: TreeNode | null = null;
constructor(val: number) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
function buildTree(values: (number | null)[]): TreeNode | null {
const first = values.length > 0 ? values[0] : null;
if (first === null) return null;
const root = new TreeNode(first);
const queue = [root];
let head = 0; // Reading from a head index keeps each dequeue O(1).
let i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
const node = queue[head++];
const left = values[i];
if (left !== null) {
node.left = new TreeNode(left);
queue.push(node.left);
}
i++;
const right = i < values.length ? values[i] : null;
if (right !== null) {
node.right = new TreeNode(right);
queue.push(node.right);
}
i++;
}
return root;
}
// Whether every value in the subtree at `node` lies strictly between `low`
// and `high` (null means no bound), with the same rule holding all the way down.
function isValid(node: TreeNode | null, low: number | null, high: number | null): boolean {
if (node === null) return true;
if ((low !== null && node.val <= low) || (high !== null && node.val >= high)) return false;
// The left subtree must also stay below this node, and the right one above it.
return isValid(node.left, low, node.val) && isValid(node.right, node.val, high);
}
// Whether the tree is a binary search tree: at every node, the whole left
// subtree is smaller and the whole right subtree is larger.
export function isValidBST(root: TreeNode | null): boolean {
return isValid(root, null, null);
}
console.log(isValidBST(buildTree([2, 1, 3]))); // true
console.log(isValidBST(buildTree([5, 1, 4, null, null, 3, 6]))); // false
console.log(isValidBST(buildTree([5, 4, 6, null, null, 3, 7]))); // false
console.log(isValidBST(buildTree([2, 2, 2]))); // falsepackage main
import "fmt"
// TreeNode is one node of a binary tree.
type TreeNode struct {
Val int
Left, Right *TreeNode
}
// buildTree builds a tree from level-order values, where nil marks a missing
// child. This is how LeetCode writes trees down.
func buildTree(values []any) *TreeNode {
if len(values) == 0 || values[0] == nil {
return nil
}
root := &TreeNode{Val: values[0].(int)}
queue := []*TreeNode{root}
i := 1
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
for i < len(values) {
node := queue[0]
queue = queue[1:]
if values[i] != nil {
node.Left = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Left)
}
i++
if i < len(values) && values[i] != nil {
node.Right = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Right)
}
i++
}
return root
}
// isValid reports whether every value in the subtree at node lies strictly
// between low and high (nil means no bound), with the same rule holding all
// the way down.
func isValid(node *TreeNode, low, high *int) bool {
if node == nil {
return true
}
if (low != nil && node.Val <= *low) || (high != nil && node.Val >= *high) {
return false
}
// The left subtree must also stay below this node, and the right one above it.
return isValid(node.Left, low, &node.Val) && isValid(node.Right, &node.Val, high)
}
// isValidBST reports whether the tree is a binary search tree: at every node,
// the whole left subtree is smaller and the whole right subtree is larger.
func isValidBST(root *TreeNode) bool {
return isValid(root, nil, nil)
}
func main() {
fmt.Println(isValidBST(buildTree([]any{2, 1, 3}))) // true
fmt.Println(isValidBST(buildTree([]any{5, 1, 4, nil, nil, 3, 6}))) // false
fmt.Println(isValidBST(buildTree([]any{5, 4, 6, nil, nil, 3, 7}))) // false
fmt.Println(isValidBST(buildTree([]any{2, 2, 2}))) // false
}using System;
using System.Collections.Generic;
public class Solution
{
public class TreeNode
{
public int Val;
public TreeNode? Left, Right;
public TreeNode(int val) => Val = val;
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode? BuildTree(int?[] values)
{
if (values.Length == 0 || values[0] is not int first) return null;
var root = new TreeNode(first);
var queue = new Queue<TreeNode>();
queue.Enqueue(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.Length)
{
var node = queue.Dequeue();
if (values[i] is int left)
{
node.Left = new TreeNode(left);
queue.Enqueue(node.Left);
}
i++;
if (i < values.Length && values[i] is int right)
{
node.Right = new TreeNode(right);
queue.Enqueue(node.Right);
}
i++;
}
return root;
}
// Whether every value in the subtree at node lies strictly between low and
// high (null means no bound), with the same rule holding all the way down.
static bool IsValid(TreeNode? node, int? low, int? high)
{
if (node == null) return true;
if ((low != null && node.Val <= low) || (high != null && node.Val >= high)) return false;
// The left subtree must also stay below this node, and the right one above it.
return IsValid(node.Left, low, node.Val) && IsValid(node.Right, node.Val, high);
}
// Whether the tree is a binary search tree: at every node, the whole left
// subtree is smaller and the whole right subtree is larger.
public static bool IsValidBST(TreeNode? root) => IsValid(root, null, null);
public static void Main()
{
Console.WriteLine(IsValidBST(BuildTree(new int?[] { 2, 1, 3 }))); // True
Console.WriteLine(IsValidBST(BuildTree(new int?[] { 5, 1, 4, null, null, 3, 6 }))); // False
Console.WriteLine(IsValidBST(BuildTree(new int?[] { 5, 4, 6, null, null, 3, 7 }))); // False
Console.WriteLine(IsValidBST(BuildTree(new int?[] { 2, 2, 2 }))); // False
}
}import java.util.ArrayDeque;
public class Solution {
static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode buildTree(Integer[] values) {
if (values.length == 0 || values[0] == null) return null;
TreeNode root = new TreeNode(values[0]);
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
TreeNode node = queue.poll();
if (values[i] != null) {
node.left = new TreeNode(values[i]);
queue.add(node.left);
}
i++;
if (i < values.length && values[i] != null) {
node.right = new TreeNode(values[i]);
queue.add(node.right);
}
i++;
}
return root;
}
// Whether every value in the subtree at node lies strictly between low and
// high (null means no bound), with the same rule holding all the way down.
static boolean isValid(TreeNode node, Integer low, Integer high) {
if (node == null) return true;
if ((low != null && node.val <= low) || (high != null && node.val >= high)) return false;
// The left subtree must also stay below this node, and the right one above it.
return isValid(node.left, low, node.val) && isValid(node.right, node.val, high);
}
// Whether the tree is a binary search tree: at every node, the whole left
// subtree is smaller and the whole right subtree is larger.
static boolean isValidBST(TreeNode root) {
return isValid(root, null, null);
}
public static void main(String[] args) {
System.out.println(isValidBST(buildTree(new Integer[] {2, 1, 3}))); // true
System.out.println(isValidBST(buildTree(new Integer[] {5, 1, 4, null, null, 3, 6}))); // false
System.out.println(isValidBST(buildTree(new Integer[] {5, 4, 6, null, null, 3, 7}))); // false
System.out.println(isValidBST(buildTree(new Integer[] {2, 2, 2}))); // false
}
}Problem
Given the root of a binary search tree and a number k, return the kth smallest value in the tree, counting from 1.
Example: for [5, 3, 6, 2, 4, null, null, 1] and k = 3, the values in order are 1, 2, 3, 4, 5, 6, so the answer is 3.
Show SolutionHide Solution
Approach
An in-order walk visits a BST's values in sorted order, so the kth value it visits is the answer. Collecting the whole walk into a list works, but it visits all n nodes when you only need k of them.
The template's inorder already walks with its own stack, so add a counter and stop the moment it reaches the kth value. Push nodes while going left, because the smallest value not yet visited is as far left as you can go. Pop one and count it. Then move to its right child, because that subtree holds the next values in order.
An In-Order Walk That Stops at K
Step 1 of 7
root
stack
- Node just pushed or popped
- Waiting on the stack
- Counted, in sorted order
Find the kth smallest value with k = 4. An in-order walk visits a BST smallest first, so the answer is the 4th node it visits. Your own stack lets it stop right there.
All 7 steps as text
- Find the kth smallest value with
k = 4. An in-order walk visits a BST smallest first, so the answer is the 4th node it visits. Your own stack lets it stop right there. - Push
5,3,2,1while going left. The smallest value not yet visited is as far left as you can go, so1ends up on top. - Pop
1, the 1st smallest, socount = 1. Its right subtree is empty, so the next value in order is waiting on the stack. - Pop
2, the 2nd smallest, socount = 2. Its right subtree is empty, so the next value in order is waiting on the stack. - Pop
3, the 3rd smallest, socount = 3. Its right child4holds the values between3and5, so they come next. Go there. - Push
4. It has no left child, so nothing in its subtree is smaller and it's next in order. - Pop
4:count = 4 = k, so return4. The walk stops with5still on the stack and never reaches6.
The walk pushes the nodes it pops plus at most one path of nodes still waiting on the stack, so it does O(h + k) work and holds O(h) nodes at once.
Time O(h + k)Space O(h)
from collections import deque
class TreeNode:
def __init__(self, val: int) -> None:
self.val = val
self.left: TreeNode | None = None
self.right: TreeNode | None = None
def build_tree(values: list[int | None]) -> TreeNode | None:
"""Builds a tree from level-order values, where None marks a missing child.
This is how LeetCode writes trees down."""
first = values[0] if values else None
if first is None:
return None
root = TreeNode(first)
queue = deque([root])
i = 1
# Hand out the remaining values two at a time, left child then right, to
# the nodes in the order they were created.
while i < len(values):
node = queue.popleft()
left = values[i]
if left is not None:
node.left = TreeNode(left)
queue.append(node.left)
i += 1
right = values[i] if i < len(values) else None
if right is not None:
node.right = TreeNode(right)
queue.append(node.right)
i += 1
return root
def kth_smallest(root: TreeNode | None, k: int) -> int:
"""The kth smallest value in the BST (k counts from 1), or -1 if the tree has
fewer than k values."""
# An in-order walk with our own stack, so it can stop after k values.
stack: list[TreeNode] = []
node = root
count = 0
while node is not None or stack:
# The smallest value not yet visited is as far left as you can go.
while node is not None:
stack.append(node)
node = node.left
node = stack.pop()
count += 1
if count == k:
return node.val
# Everything in its right subtree comes next in sorted order.
node = node.right
return -1
if __name__ == "__main__":
print(kth_smallest(build_tree([3, 1, 4, None, 2]), 1)) # 1
root = build_tree([5, 3, 6, 2, 4, None, None, 1])
print(kth_smallest(root, 3)) # 3
print(kth_smallest(root, 6)) # 6class TreeNode {
val: number;
left: TreeNode | null = null;
right: TreeNode | null = null;
constructor(val: number) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
function buildTree(values: (number | null)[]): TreeNode | null {
const first = values.length > 0 ? values[0] : null;
if (first === null) return null;
const root = new TreeNode(first);
const queue = [root];
let head = 0; // Reading from a head index keeps each dequeue O(1).
let i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
const node = queue[head++];
const left = values[i];
if (left !== null) {
node.left = new TreeNode(left);
queue.push(node.left);
}
i++;
const right = i < values.length ? values[i] : null;
if (right !== null) {
node.right = new TreeNode(right);
queue.push(node.right);
}
i++;
}
return root;
}
// The kth smallest value in the BST (k counts from 1), or -1 if the tree has
// fewer than k values.
export function kthSmallest(root: TreeNode | null, k: number): number {
// An in-order walk with our own stack, so it can stop after k values.
const stack: TreeNode[] = [];
let node = root;
let count = 0;
while (node !== null || stack.length > 0) {
// The smallest value not yet visited is as far left as you can go.
while (node !== null) {
stack.push(node);
node = node.left;
}
node = stack.pop()!;
count++;
if (count === k) return node.val;
// Everything in its right subtree comes next in sorted order.
node = node.right;
}
return -1;
}
console.log(kthSmallest(buildTree([3, 1, 4, null, 2]), 1)); // 1
const root = buildTree([5, 3, 6, 2, 4, null, null, 1]);
console.log(kthSmallest(root, 3)); // 3
console.log(kthSmallest(root, 6)); // 6package main
import "fmt"
// TreeNode is one node of a binary tree.
type TreeNode struct {
Val int
Left, Right *TreeNode
}
// buildTree builds a tree from level-order values, where nil marks a missing
// child. This is how LeetCode writes trees down.
func buildTree(values []any) *TreeNode {
if len(values) == 0 || values[0] == nil {
return nil
}
root := &TreeNode{Val: values[0].(int)}
queue := []*TreeNode{root}
i := 1
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
for i < len(values) {
node := queue[0]
queue = queue[1:]
if values[i] != nil {
node.Left = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Left)
}
i++
if i < len(values) && values[i] != nil {
node.Right = &TreeNode{Val: values[i].(int)}
queue = append(queue, node.Right)
}
i++
}
return root
}
// kthSmallest returns the kth smallest value in the BST (k counts from 1), or
// -1 if the tree has fewer than k values.
func kthSmallest(root *TreeNode, k int) int {
// An in-order walk with our own stack, so it can stop after k values.
stack := []*TreeNode{}
node := root
count := 0
for node != nil || len(stack) > 0 {
// The smallest value not yet visited is as far left as you can go.
for node != nil {
stack = append(stack, node)
node = node.Left
}
node = stack[len(stack)-1]
stack = stack[:len(stack)-1]
count++
if count == k {
return node.Val
}
// Everything in its right subtree comes next in sorted order.
node = node.Right
}
return -1
}
func main() {
fmt.Println(kthSmallest(buildTree([]any{3, 1, 4, nil, 2}), 1)) // 1
root := buildTree([]any{5, 3, 6, 2, 4, nil, nil, 1})
fmt.Println(kthSmallest(root, 3)) // 3
fmt.Println(kthSmallest(root, 6)) // 6
}using System;
using System.Collections.Generic;
public class Solution
{
public class TreeNode
{
public int Val;
public TreeNode? Left, Right;
public TreeNode(int val) => Val = val;
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode? BuildTree(int?[] values)
{
if (values.Length == 0 || values[0] is not int first) return null;
var root = new TreeNode(first);
var queue = new Queue<TreeNode>();
queue.Enqueue(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.Length)
{
var node = queue.Dequeue();
if (values[i] is int left)
{
node.Left = new TreeNode(left);
queue.Enqueue(node.Left);
}
i++;
if (i < values.Length && values[i] is int right)
{
node.Right = new TreeNode(right);
queue.Enqueue(node.Right);
}
i++;
}
return root;
}
// The kth smallest value in the BST (k counts from 1), or -1 if the tree has
// fewer than k values.
public static int KthSmallest(TreeNode? root, int k)
{
// An in-order walk with our own stack, so it can stop after k values.
var stack = new Stack<TreeNode>();
var node = root;
int count = 0;
while (node != null || stack.Count > 0)
{
// The smallest value not yet visited is as far left as you can go.
while (node != null)
{
stack.Push(node);
node = node.Left;
}
node = stack.Pop();
count++;
if (count == k) return node.Val;
// Everything in its right subtree comes next in sorted order.
node = node.Right;
}
return -1;
}
public static void Main()
{
Console.WriteLine(KthSmallest(BuildTree(new int?[] { 3, 1, 4, null, 2 }), 1)); // 1
var root = BuildTree(new int?[] { 5, 3, 6, 2, 4, null, null, 1 });
Console.WriteLine(KthSmallest(root, 3)); // 3
Console.WriteLine(KthSmallest(root, 6)); // 6
}
}import java.util.ArrayDeque;
public class Solution {
static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
// Builds a tree from level-order values, where null marks a missing child.
// This is how LeetCode writes trees down.
static TreeNode buildTree(Integer[] values) {
if (values.length == 0 || values[0] == null) return null;
TreeNode root = new TreeNode(values[0]);
ArrayDeque<TreeNode> queue = new ArrayDeque<>();
queue.add(root);
int i = 1;
// Hand out the remaining values two at a time, left child then right, to
// the nodes in the order they were created.
while (i < values.length) {
TreeNode node = queue.poll();
if (values[i] != null) {
node.left = new TreeNode(values[i]);
queue.add(node.left);
}
i++;
if (i < values.length && values[i] != null) {
node.right = new TreeNode(values[i]);
queue.add(node.right);
}
i++;
}
return root;
}
// The kth smallest value in the BST (k counts from 1), or -1 if the tree has
// fewer than k values.
static int kthSmallest(TreeNode root, int k) {
// An in-order walk with our own stack, so it can stop after k values.
ArrayDeque<TreeNode> stack = new ArrayDeque<>();
TreeNode node = root;
int count = 0;
while (node != null || !stack.isEmpty()) {
// The smallest value not yet visited is as far left as you can go.
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
count++;
if (count == k) return node.val;
// Everything in its right subtree comes next in sorted order.
node = node.right;
}
return -1;
}
public static void main(String[] args) {
System.out.println(kthSmallest(buildTree(new Integer[] {3, 1, 4, null, 2}), 1)); // 1
TreeNode root = buildTree(new Integer[] {5, 3, 6, 2, 4, null, null, 1});
System.out.println(kthSmallest(root, 3)); // 3
System.out.println(kthSmallest(root, 6)); // 6
}
}Common Pitfalls
- Validating by comparing each node only with its children. The rule covers whole subtrees, so pass bounds down from the ancestors.
- Starting the bounds at the smallest and largest integers when the tree can hold those exact values. A node equal to
Integer.MIN_VALUEthen fails a check it should pass. Usenullfor no bound, or a wider type. - Leaving duplicates undefined. Decide whether equal values go left, go right or aren't allowed, and make search, insert and validation agree.
- Assuming a plain BST is O(log n). Sorted input builds a linked list, and every operation becomes O(n). When you can't control the insertion order, use a self-balancing tree such as your language's ordered map.
- Collecting the whole in-order list to answer kth smallest. Walk with a stack and stop after k values.
Practice
More problems to try on your own.