Threaded Tree Patterns & Practice
Practice problems that test your understanding of threaded binary trees. Try to solve each problem before opening the solution. Each solution includes a detailed explanation and an animation where applicable.
Problem 1: In-order Successor in a Threaded BST
Given a node in a right-threaded BST, return its in-order successor. Your solution must run in O(1) amortized time and use O(1) space.
struct ThreadedNode {
int val;
ThreadedNode* left;
ThreadedNode* right;
bool rightThread; // true = right points to successor
};
ThreadedNode* inorderSuccessor(ThreadedNode* node) {
// Your code here
}
Hint: There are only two cases. What does rightThread tell you?
Show Solution
Approach
If the node is right-threaded (rightThread == true), the right pointer is the successor, just return it directly. That's the whole point of threading.
If the node has a real right child (rightThread == false), the successor is the leftmost node in the right subtree. Go right once, then keep going left until you can't.
ThreadedNode* inorderSuccessor(ThreadedNode* node) {
// Case 1: Thread exists — follow it directly
if (node->rightThread)
return node->right; // O(1)
// Case 2: Has right child — find leftmost in right subtree
ThreadedNode* succ = node->right;
while (succ->left && !succ->leftThread)
succ = succ->left;
return succ;
}
Why O(1) Amortized?
Case 1 is O(1). Case 2 could be O(h) for a single call, but if you traverse the entire tree by repeatedly calling inorderSuccessor, each edge is visited at most twice total. Over n calls, the total work is O(n), so amortized O(1) per call.
Complexity
| Time | O(1) amortized |
| Space | O(1) |
Problem 2: Convert BST to Right-Threaded BST
Given a standard BST, convert it in-place to a right-threaded BST. All null right pointers of nodes that have an in-order successor should point to that successor, with rightThread set to true.
void convertToThreaded(ThreadedNode* root) {
// Your code here — modify the tree in-place
}
Hint: Think about reverse in-order traversal. What information does each node need from the node visited just before it?
Show Solution
Approach: Reverse In-order with Previous Pointer
The trick is to do a reverse in-order traversal (right, root, left). This visits nodes from largest to smallest. We maintain a prev pointer to the previously visited node. When we visit a node, if its right child is null, we thread it to prev (which is its in-order successor, since we're going in reverse).
void convertToThreaded(ThreadedNode* root) {
ThreadedNode* prev = nullptr;
std::function<void(ThreadedNode*)> reverseInorder =
[&](ThreadedNode* node) {
if (!node) return;
reverseInorder(node->right); // visit right first
// If right is null, thread it to the in-order successor
if (!node->right) {
node->right = prev; // thread to successor
node->rightThread = true;
} else {
node->rightThread = false;
}
prev = node; // I'm the prev for the next node
reverseInorder(node->left); // visit left
};
reverseInorder(root);
}
Why Reverse In-order?
In reverse in-order (right → root → left), the previously visited node is always the in-order successor of the current node. So when we encounter a null right pointer, we already have the correct successor in prev.
If we used forward in-order, prev would be the predecessor, useful for left-threading, but not right-threading.
Walkthrough
For a BST [4, 2, 6, 1, 3, 5, 7], in reverse in-order we visit: 7, 6, 5, 4, 3, 2, 1.
- Visit 7: right is null,
previs null → thread to null (no successor). Setprev = 7. - Visit 6: right is 7 (real child) → not threaded. Set
prev = 6. - Visit 5: right is null → thread to
prev= 6. Setprev = 5. - Visit 4: right is 6 (real child) → not threaded. Set
prev = 4. - Visit 3: right is null → thread to
prev= 4. Setprev = 3. - Visit 2: right is 3 (real child) → not threaded. Set
prev = 2. - Visit 1: right is null → thread to
prev= 2. Setprev = 1.
Result: 1→2, 3→4, 5→6, 7→null. Correct!
Complexity
| Time | O(n) — visit each node once |
| Space | O(h) — recursion stack |
Problem 3: Check if a Binary Tree is Threaded
Given a binary tree with leftThread and rightThread flags, verify that all threads point to the correct in-order predecessor/successor. Return true if the threading is valid.
bool isValidThreading(ThreadedNode* root) {
// Your code here
}
Hint: Do a regular in-order traversal and check each thread against the actual predecessor/successor.
Show Solution
Approach: In-order Traversal with Validation
Do a standard in-order traversal (using a stack, since the tree might be incorrectly threaded and we can't trust the threads). Track the previous node. For each node:
- If
rightThreadis true, check thatrightpoints to the actual in-order successor. - If
leftThreadis true, check thatleftpoints to the previous node (in-order predecessor).
bool isValidThreading(ThreadedNode* root) {
// Collect in-order sequence using stack (don't trust threads)
std::vector<ThreadedNode*> inorder;
std::stack<ThreadedNode*> stk;
auto* cur = root;
while (cur || !stk.empty()) {
while (cur && !cur->leftThread) {
stk.push(cur);
cur = cur->left;
}
if (cur && cur->leftThread) stk.push(cur);
if (stk.empty()) break;
cur = stk.top(); stk.pop();
inorder.push_back(cur);
cur = cur->rightThread ? nullptr : cur->right;
}
// Validate each thread
for (int i = 0; i < (int)inorder.size(); i++) {
auto* node = inorder[i];
if (node->rightThread) {
auto* expected = (i + 1 < (int)inorder.size())
? inorder[i + 1] : nullptr;
if (node->right != expected) return false;
}
if (node->leftThread) {
auto* expected = (i - 1 >= 0)
? inorder[i - 1] : nullptr;
if (node->left != expected) return false;
}
}
return true;
}
Complexity
| Time | O(n) |
| Space | O(n) for the inorder array |
Problem 4: Delete a Node from a Threaded BST
Implement deletion in a right-threaded BST. After deletion, all threads must remain valid. Handle all three BST deletion cases (leaf, one child, two children).
ThreadedNode* deleteNode(ThreadedNode* root, int key) {
// Your code here
}
Hint: The tricky part is updating threads of the deleted node's in-order predecessor. When you remove a node, its predecessor's thread (if any) needs to be redirected.
Show Solution
Approach
Deletion in a threaded BST follows standard BST deletion with extra thread maintenance. The three cases:
Case 1: Leaf Node
The simplest case. If the leaf is a left child, its parent's left becomes a thread to the leaf's in-order predecessor. If the leaf is a right child, its parent's right becomes a thread to the leaf's in-order successor (which the leaf was threaded to).
Case 2: One Child
Replace the node with its child. But we must also fix the thread of the child's predecessor or successor. Specifically, the deleted node's in-order predecessor's thread needs to point to the deleted node's successor (if it was threaded to the deleted node).
Case 3: Two Children
Replace with in-order successor (which, in a threaded tree, we can find in O(1) if the node is threaded, or O(h) otherwise). Then delete the successor from its original position (which reduces to Case 1 or Case 2).
ThreadedNode* deleteNode(ThreadedNode* root, int key) {
ThreadedNode* parent = nullptr;
ThreadedNode* cur = root;
bool found = false;
// Search for the node
while (cur) {
if (key == cur->val) { found = true; break; }
parent = cur;
if (key < cur->val) {
if (!cur->leftThread) cur = cur->left;
else break;
} else {
if (!cur->rightThread) cur = cur->right;
else break;
}
}
if (!found) return root;
// Case 3: Two children — replace with inorder successor
if (!cur->leftThread && cur->left &&
!cur->rightThread && cur->right) {
// Find in-order successor
ThreadedNode* succParent = cur;
ThreadedNode* succ = cur->right;
while (!succ->leftThread && succ->left) {
succParent = succ;
succ = succ->left;
}
cur->val = succ->val; // copy value
// Now delete succ (which has at most one child)
parent = succParent;
cur = succ;
}
// Cases 1 & 2: Leaf or one child
// Find the in-order predecessor and successor for thread fixup
ThreadedNode* pred = cur->leftThread ? cur->left : nullptr;
ThreadedNode* succ = cur->rightThread ? cur->right : nullptr;
ThreadedNode* child = nullptr;
if (!cur->leftThread && cur->left)
child = cur->left;
else if (!cur->rightThread && cur->right)
child = cur->right;
// Fix the predecessor's thread
if (pred) {
// pred was threaded to cur; now thread to cur's successor
if (pred->rightThread && pred->right == cur)
pred->right = succ;
}
// Fix the successor's thread
if (succ) {
// succ's left thread might point to cur
if (succ->leftThread && succ->left == cur)
succ->left = pred;
}
// Reattach child to parent
if (!parent) return child; // deleting root
if (parent->left == cur) {
if (!child) {
parent->leftThread = true;
parent->left = pred; // thread to predecessor
} else {
parent->left = child;
}
} else {
if (!child) {
parent->rightThread = true;
parent->right = succ; // thread to successor
} else {
parent->right = child;
}
}
delete cur;
return root;
}
The Key Insight
The extra complexity compared to standard BST deletion is thread maintenance. When you remove a node, up to two threads may need updating: the predecessor's right thread and the successor's left thread. Always find the predecessor and successor before removing the node.
Complexity
| Time | O(h) — search + successor finding |
| Space | O(1) |
Problem 5: Find Kth Smallest in a Threaded BST
Given the root of a right-threaded BST, return the kth smallest element. Your solution should use O(1) extra space (no stack or recursion).
int kthSmallest(ThreadedNode* root, int k) {
// Your code here — O(1) space!
}
Hint: You already know how to traverse a threaded BST without a stack. How many nodes do you need to visit?
Show Solution
Approach
This is where threaded trees shine. In a standard BST, finding the kth smallest requires O(h) stack space for in-order traversal. In a threaded BST, we can do the entire traversal with O(1) space, just follow the threading!
- Start at the leftmost node (smallest element).
- Use the
inorderSuccessorfunction from Problem 1 to step through nodes one by one. - Count to k.
int kthSmallest(ThreadedNode* root, int k) {
// Go to the leftmost (smallest) node
ThreadedNode* cur = root;
while (cur->left && !cur->leftThread)
cur = cur->left;
// Walk through k nodes using threading
int count = 0;
while (cur) {
count++;
if (count == k) return cur->val;
// Move to in-order successor
if (cur->rightThread) {
cur = cur->right; // follow thread
} else {
// Go to leftmost node in right subtree
cur = cur->right;
while (cur && cur->left && !cur->leftThread)
cur = cur->left;
}
}
return -1; // k is larger than tree size
}
Why This Matters
Compare with the standard BST approach:
| Standard BST | Threaded BST | |
|---|---|---|
| Time | O(h + k) | O(h + k) — same |
| Space | O(h) — stack | O(1) — no stack! |
The time is the same, but we eliminated all auxiliary space. This is exactly the kind of improvement threaded trees are built for.
Problem 6: Reverse In-order Traversal (Largest to Smallest)
Given a double-threaded BST (both left and right threads), print all values in reverse in-order (largest to smallest) using O(1) extra space.
void reverseInorder(ThreadedNode* root) {
// Your code here — O(1) space, no recursion
}
Hint: Mirror the standard threaded traversal. Where does the standard one start? Where should this one start?
Show Solution
Approach
The standard threaded traversal starts at the leftmost node and follows right threads. The reverse version starts at the rightmost node and follows left threads (which point to in-order predecessors in a double-threaded tree).
void reverseInorder(ThreadedNode* root) {
// Go to the rightmost (largest) node
ThreadedNode* cur = root;
while (cur->right && !cur->rightThread)
cur = cur->right;
// Traverse using left threads
while (cur) {
visit(cur); // process current node
// Move to in-order predecessor
if (cur->leftThread) {
cur = cur->left; // follow left thread to predecessor
} else if (cur->left) {
// Go to rightmost node in left subtree
cur = cur->left;
while (cur->right && !cur->rightThread)
cur = cur->right;
} else {
cur = nullptr; // no predecessor — we're done
}
}
}
Why Double Threading Helps
Without left threads, reverse traversal would require a stack (to go back to the parent). Double threading gives us predecessor links for free, enabling both forward and reverse traversal with O(1) space.
Complexity
| Time | O(n) |
| Space | O(1) |
Interactive: Walk Through a Threaded BST
Step through the in-order traversal of a right-threaded BST. Notice how threads (dashed orange) let us jump back to the successor without a stack.
▶ Threaded BST In-order Walk
Summary
- Successor in O(1): If threaded, follow the thread. Otherwise, leftmost in right subtree.
- Converting to threaded: Reverse in-order traversal with a
prevpointer. - Validation: Collect in-order sequence, verify each thread matches the expected predecessor/successor.
- Deletion: Standard BST deletion + thread maintenance (update predecessor/successor threads).
- Kth smallest: Walk with O(1) space using threading, the killer application.
- Double threading: Enables both forward and reverse O(1)-space traversal.