You should aim for a solution as good or better than O(n) time and O(n) space, where n is the number of nodes in the given tree.
Hint 1
A naive solution would be to store the node values in an array, sort it, and then return the k-th value from the sorted array. This would be an O(nlogn) solution due to sorting. Can you think of a better way? Maybe you should try one of the traversal technique.
Hint 2
We can use the Depth First Search (DFS) algorithm to traverse the tree. Since the tree is a Binary Search Tree (BST), we can leverage its structure and perform an in-order traversal, where we first visit the left subtree, then the current node, and finally the right subtree. Why? Because we need the k-th smallest integer, and by visiting the left subtree first, we ensure that we encounter smaller nodes before the current node. How can you implement this?
Hint 3
We keep a counter variable cnt to track the position of the current node in the ascending order of values. When cnt == k, we store the current node's value in a global variable and return. This allows us to identify and return the k-th smallest element during the in-order traversal.
Company Tags
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Binary Search Tree (BST) - Understanding the BST property where left subtree values are smaller and right subtree values are larger than the root
Tree Traversal (DFS) - Ability to traverse trees using depth-first search techniques
Inorder Traversal - Knowing that inorder traversal of a BST produces values in sorted order
Stack Data Structure - For implementing iterative tree traversal solutions
1. Brute Force
Intuition
A Binary Search Tree (BST) has a special property:
Left subtree < root < right subtree But this brute-force method does not use the BST property.
We simply:
Traverse the entire tree and collect all node values.
Sort the collected values.
The k-th smallest element is at index k-1 in the sorted list.
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @param {number} k
* @return {number}
*/kthSmallest(root, k){const arr =[];this.dfs(root, arr);
arr.sort((a, b)=> a - b);return arr[k -1];}/**
* @param {TreeNode} node
* @param {number[]} arr
*/dfs(node, arr){if(!node)return;
arr.push(node.val);this.dfs(node.left, arr);this.dfs(node.right, arr);}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintKthSmallest(TreeNode root,int k){List<int> arr =newList<int>();Dfs(root, arr);
arr.Sort();return arr[k -1];}privatevoidDfs(TreeNode node,List<int> arr){if(node ==null)return;
arr.Add(node.val);Dfs(node.left, arr);Dfs(node.right, arr);}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funckthSmallest(root *TreeNode, k int)int{var arr []intvar dfs func(node *TreeNode)
dfs =func(node *TreeNode){if node ==nil{return}dfs(node.Left)
arr =append(arr, node.Val)dfs(node.Right)}dfs(root)return arr[k-1]}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {privateval arr = mutableListOf<Int>()funkthSmallest(root: TreeNode?, k: Int): Int {dfs(root)return arr[k -1]}privatefundfs(node: TreeNode?){if(node ==null){return}dfs(node.left)
arr.add(node.`val`)dfs(node.right)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funckthSmallest(_ root:TreeNode?,_ k:Int)->Int{var arr =[Int]()funcdfs(_ node:TreeNode?){guardlet node = node else{return}
arr.append(node.val)dfs(node.left)dfs(node.right)}dfs(root)
arr.sort()return arr[k -1]}}
Time & Space Complexity
Time complexity: O(nlogn)
Space complexity: O(n)
2. Inorder Traversal
Intuition
A Binary Search Tree (BST) has an important property:
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintKthSmallest(TreeNode root,int k){List<int> arr =newList<int>();Dfs(root, arr);return arr[k -1];}privatevoidDfs(TreeNode node,List<int> arr){if(node ==null)return;Dfs(node.left, arr);
arr.Add(node.val);Dfs(node.right, arr);}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funckthSmallest(root *TreeNode, k int)int{var arr []intvar dfs func(node *TreeNode)
dfs =func(node *TreeNode){if node ==nil{return}dfs(node.Left)
arr =append(arr, node.Val)dfs(node.Right)}dfs(root)return arr[k-1]}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {privateval arr = mutableListOf<Int>()funkthSmallest(root: TreeNode?, k: Int): Int {dfs(root)return arr[k -1]}privatefundfs(node: TreeNode?){if(node ==null){return}dfs(node.left)
arr.add(node.`val`)dfs(node.right)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funckthSmallest(_ root:TreeNode?,_ k:Int)->Int{var arr =[Int]()funcdfs(_ node:TreeNode?){guardlet node = node else{return}dfs(node.left)
arr.append(node.val)dfs(node.right)}dfs(root)return arr[k -1]}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Recursive DFS (Optimal)
Intuition
In a BST, the inorder traversal (Left → Node → Right) naturally visits nodes in sorted order.
So instead of storing all values, we can:
Traverse the tree in inorder,
Count nodes as we visit them,
Stop as soon as we visit the k-th smallest node.
This avoids extra space and stops early, making it more optimal.
Algorithm
Keep a counter cnt = k.
Perform an inorder DFS:
Go left.
When visiting a node:
Decrease cnt.
If cnt == 0, record this node's value (this is the k-th smallest).
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintKthSmallest(TreeNode root,int k){int[] tmp =newint[2];
tmp[0]= k;Dfs(root, tmp);return tmp[1];}privatevoidDfs(TreeNode node,int[] tmp){if(node ==null)return;Dfs(node.left, tmp);
tmp[0]--;if(tmp[0]==0){
tmp[1]= node.val;return;}Dfs(node.right, tmp);}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funckthSmallest(root *TreeNode, k int)int{
cnt, res := k,0var dfs func(node *TreeNode)
dfs =func(node *TreeNode){if node ==nil{return}dfs(node.Left)
cnt--if cnt ==0{
res = node.Val
return}dfs(node.Right)}dfs(root)return res
}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {privatevar cnt =0privatevar res =0funkthSmallest(root: TreeNode?, k: Int): Int {
cnt = k
dfs(root)return res
}privatefundfs(node: TreeNode?){if(node ==null){return}dfs(node.left)
cnt--if(cnt ==0){
res = node.`val`
return}dfs(node.right)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funckthSmallest(_ root:TreeNode?,_ k:Int)->Int{var cnt = k
var res = root!.val
funcdfs(_ node:TreeNode?){guardlet node = node else{return}dfs(node.left)
cnt -=1if cnt ==0{
res = node.val
return}dfs(node.right)}dfs(root)return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
4. Iterative DFS (Optimal)
Intuition
In a BST, an inorder traversal (left -> node -> right) gives nodes in sorted order. Instead of recursion, we simulate this traversal with a stack:
Push all left nodes (go as deep as possible).
Pop the top node - this is the next smallest value.
Move to its right subtree and repeat.
When we pop the k-th node, that's our answer.
This way, we only visit nodes until we reach the k-th smallest — no need to traverse the whole tree.
Algorithm
Initialize an empty stack and set curr = root.
While either stack is not empty or curr is not null:
Push all left nodes into the stack (curr = curr.left).
Pop from the stack - this is the next smallest node.
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintKthSmallest(TreeNode root,int k){Stack<TreeNode> stack =newStack<TreeNode>();TreeNode curr = root;while(stack.Count >0|| curr !=null){while(curr !=null){
stack.Push(curr);
curr = curr.left;}
curr = stack.Pop();
k--;if(k ==0){return curr.val;}
curr = curr.right;}return-1;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funckthSmallest(root *TreeNode, k int)int{
stack :=[]*TreeNode{}
curr := root
forlen(stack)>0|| curr !=nil{for curr !=nil{
stack =append(stack, curr)
curr = curr.Left
}
curr = stack[len(stack)-1]
stack = stack[:len(stack)-1]
k--if k ==0{return curr.Val
}
curr = curr.Right
}return0}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funkthSmallest(root: TreeNode?, k: Int): Int {val stack = mutableListOf<TreeNode>()var curr: TreeNode?= root
var k = k
while(stack.isNotEmpty()|| curr !=null){while(curr !=null){
stack.add(curr)
curr = curr.left
}
curr = stack.removeLast()
k--if(k ==0){return curr.`val`
}
curr = curr.right
}return0}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funckthSmallest(_ root:TreeNode?,_ k:Int)->Int{var stack =[TreeNode]()var curr = root
var k = k
while!stack.isEmpty || curr !=nil{while curr !=nil{
stack.append(curr!)
curr = curr?.left}
curr = stack.removeLast()
k -=1if k ==0{return curr!.val
}
curr = curr?.right}return-1}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
5. Morris Traversal
Intuition
Inorder traversal of a BST gives values in sorted order, so the k-th visited node is the k-th smallest. But recursion and stacks use extra space.
Morris Traversal allows us to perform inorder traversal using O(1) extra space, by temporarily creating a "thread" (a right pointer) from a node's predecessor back to the node.
For each node:
If it has no left child - visit it directly.
If it has a left child - find its inorder predecessor.
If the predecessor's right pointer is empty - create a temporary link to the current node and move left.
If the predecessor's right pointer already points to the current node - remove the link, visit the node, and move right.
We decrement k each time we "visit" a node. The node where k becomes 0 is the k-th smallest.
This works because we simulate the inorder order without extra memory.
Algorithm
Set curr = root.
While curr is not null:
Case 1: No left child
Visit curr (decrement k).
If k == 0, return curr.val.
Move to curr.right.
Case 2: Has a left child
Find the inorder predecessor (pred = rightmost node in left subtree).
If pred.right is null:
Create a temporary thread: pred.right = curr.
Move curr to its left child.
Else (thread already exists):
Remove the thread: pred.right = null.
Visit curr (decrement k).
If k == 0, return curr.val.
Move to curr.right.
If traversal ends without finding k nodes, return -1.
/**
* Definition for a binary tree node.
* class TreeNode {
* constructor(val = 0, left = null, right = null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/classSolution{/**
* @param {TreeNode} root
* @param {number} k
* @return {number}
*/kthSmallest(root, k){let curr = root;while(curr){if(!curr.left){
k--;if(k ===0)return curr.val;
curr = curr.right;}else{let pred = curr.left;while(pred.right && pred.right !== curr) pred = pred.right;if(!pred.right){
pred.right = curr;
curr = curr.left;}else{
pred.right =null;
k--;if(k ===0)return curr.val;
curr = curr.right;}}}return-1;}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicintKthSmallest(TreeNode root,int k){TreeNode curr = root;while(curr !=null){if(curr.left ==null){
k--;if(k ==0)return curr.val;
curr = curr.right;}else{TreeNode pred = curr.left;while(pred.right !=null&& pred.right != curr)
pred = pred.right;if(pred.right ==null){
pred.right = curr;
curr = curr.left;}else{
pred.right =null;
k--;if(k ==0)return curr.val;
curr = curr.right;}}}return-1;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funckthSmallest(root *TreeNode, k int)int{
curr := root
for{if curr.Left ==nil{
k--if k ==0{return curr.Val
}
curr = curr.Right
}else{
pred := curr.Left
for pred.Right !=nil&& pred.Right != curr {
pred = pred.Right
}if pred.Right ==nil{
pred.Right = curr
curr = curr.Left
}else{
pred.Right =nil
k--if k ==0{return curr.Val
}
curr = curr.Right
}}}}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funkthSmallest(root: TreeNode?, k: Int): Int {var curr: TreeNode?= root
var k = k
while(true){if(curr?.left ==null){
k--if(k ==0){return curr!!.`val`
}
curr = curr?.right
}else{var pred = curr.left
while(pred?.right !=null&& pred.right != curr){
pred = pred.right
}if(pred?.right ==null){
pred.right = curr
curr = curr.left
}else{
pred.right =null
k--if(k ==0){return curr!!.`val`
}
curr = curr.right
}}}}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funckthSmallest(_ root:TreeNode?,_ k:Int)->Int{var curr = root
var k = k
while curr !=nil{if curr?.left==nil{
k -=1if k ==0{return curr!.val
}
curr = curr?.right}else{var pred = curr?.leftwhile pred?.right!=nil&& pred?.right!== curr {
pred = pred?.right}if pred?.right==nil{
pred?.right= curr
curr = curr?.left}else{
pred?.right=nil
k -=1if k ==0{return curr!.val
}
curr = curr?.right}}}return-1}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1)
Common Pitfalls
Using Pre-Order or Post-Order Instead of In-Order
A BST's defining property is that in-order traversal yields sorted values. Using pre-order or post-order traversal does not produce sorted output, so you cannot simply pick the k-th visited node. Always use in-order traversal (left, node, right) to get elements in ascending order.
Off-by-One Errors with k
The k-th smallest element is at index k-1 in a zero-indexed array or corresponds to the k-th decrement of a counter starting at k. Mixing up one-based and zero-based indexing leads to returning the wrong element. Be consistent: if using a counter, return when it reaches zero; if using an array, access index k-1.
Not Stopping Early
When counting nodes during traversal, there is no need to visit the entire tree once the k-th element is found. Failing to return early means wasted computation, especially in large trees where k is small. Use a flag or check the counter after each visit to terminate as soon as possible.