019 — Validate Binary Search Tree¶
Source¶
- Original source: Classic cross-topic capstone interview problem (BST property checking)
- Platform: LeetCode — https://leetcode.com/problems/validate-binary-search-tree/
- Topic: trees + recursion
- Difficulty: Medium
- Pattern: Range-passing recursion (valid-bounds propagation)
- Basic idea inherited from: এটা একটা cross-topic capstone — এখানে মেশে 07 trees (একটা binary tree, BST-র property, node-এর left/right structure) আর 06 recursion (প্রতিটা subtree-কে একই প্রশ্ন জিজ্ঞেস করা, সাথে একটা বৈধ range বহন করা)। দুই tool জোড়া লাগলেই range-passing validation।
1. Problem in simple words¶
তোমাকে একটা binary tree-র root দেওয়া আছে। তোমার কাজ: বলো এটা একটা বৈধ Binary Search Tree (BST) কিনা। BST-র নিয়ম: প্রতিটা node-এর জন্য — তার পুরো left subtree-র সব মান node-এর চেয়ে ছোট, আর তার পুরো right subtree-র সব মান node-এর চেয়ে বড় হতে হবে। শুধু True/False চাই।
2. Which basic idea does this inherit from?¶
এই problem দুটো আগের chapter-এর tool জোড়া দেয়:
- 07 trees থেকে: একটা binary tree-র উপর চলাফেরা — প্রতিটা node-এর
val,left,right, আর BST-র অর্ডারিং property-টা কী মানে। - 06 recursion থেকে: একই প্রশ্নকে subtree-তে নামানো, আর সাথে একটা অতিরিক্ত state (এই node-এর মান কোন
(low, high)range-এ থাকতে হবে) বহন করে নিচে পাঠানো।
একা tree-traversal তোমাকে দেয় সব node ছোঁয়ার ক্ষমতা; একা recursion দেয় বৈধ range নিচে passing করার ক্ষমতা। দুটো মিললে O(n) এক-পাস validation।
3. What is the hidden math or logic?¶
লুকানো logic একটা range invariant: শুধু "left child < node < right child" দেখা যথেষ্ট নয় — পুরো subtree জুড়ে নিয়ম ধরে রাখতে হয়। তাই প্রতিটা node-কে একটা বৈধ খোলা ব্যবধান (low, high) দিয়ে যাচাই করি — node-এর মান low < val < high হতে হবে। নিচে নামার সময় range সংকুচিত করি —
left child -এ যাও: range হয় (low, node.val) -- এখন উপরের সীমা node.val
right child -এ যাও: range হয় (node.val, high) -- এখন নিচের সীমা node.val
root শুরু করে (-inf, +inf) দিয়ে। এই range নিচে গড়িয়ে গিয়ে ancestor-দের সব constraint একসাথে চাপায়।
4. Which data structure is needed?¶
বড় কোনো data structure লাগে না — শুধু recursion আর প্রতিটা call-এ দুটো bound (low, high)। গাছটা 07 trees-এর node structure (val/left/right), আর range বহন করা 06 recursion-এর কাজ। implicit call-stack বাদে extra memory নেই।
5. Why this data structure fits¶
BST-র নিয়ম recursive ভাবেই সংজ্ঞায়িত — "বৈধ BST = root ঠিক range-এ + left subtree বৈধ + right subtree বৈধ"। এই self-similar কাঠামো recursion-এর সাথে নিখুঁত খাপ খায় (06 recursion)। আর প্রতিটা subtree-কে শুধু একটা সংকুচিত (low, high) জানালেই সেটা স্বয়ংসম্পূর্ণভাবে যাচাই হয় — কোনো global array বা extra structure লাগে না। তাই tree + দুটো bound-ই যথেষ্ট।
6. Real-life analogy¶
ভাবো একটা সাংগঠনিক চার্ট, যেখানে প্রতিটা পদের একটা "অনুমোদিত বেতন-পরিসর" আছে যা উপরের বসদের সিদ্ধান্ত থেকে নেমে আসে। তুমি ওপর থেকে নিচে নামছ — বাঁ শাখায় গেলে সর্বোচ্চ সীমা কমে (current বসের চেয়ে কম হতে হবে), ডান শাখায় গেলে সর্বনিম্ন সীমা বাড়ে। প্রতিটা পদের বেতন তার গড়িয়ে-আসা পরিসরের ভেতরে থাকতে হবে — একটাও বাইরে গেলে পুরো চার্ট অবৈধ।
7. Visual explanation¶
একটা অবৈধ গাছ দেখি — চোখে ঠিক মনে হলেও range ধরে ফেলে:
5 range নিচে গড়ায়:
/ \
1 4 5 -> (-inf, +inf) 5 ঠিক
/ \ 1 -> (-inf, 5) 1 ঠিক
3 6 4 -> (5, +inf) 4 < 5 ? না! 4 > 5 লাগত
-> অবৈধ (4 আসলে 5-এর ডানে, তাই 5-এর চেয়ে বড় হতে হবে)
শুধু "4-এর child 3<4<6" দেখলে ভুল করে valid বলত; কিন্তু 4 তো 5-এর right subtree-তে, তাই তাকে 5-এর চেয়ে বড় হতেই হতো — range invariant সেটা ধরে।
8. Example input and output¶
Input : [2,1,3] -> Output: True # 1 < 2 < 3
Input : [5,1,4,null,null,3,6] -> Output: False # 4 root 5-এর ডানে, কিন্তু 4 < 5
Edge case 1 (একটা node): [1] -> Output: True
Edge case 2 (সমান মান): [2,2,2] -> Output: False # BST-তে duplicate (==) চলে না, strict <
9. Brute force thinking¶
প্রথম (ভুল) সরল চিন্তা: প্রতিটা node-এ শুধু সরাসরি child-দের সাথে তুলনা করি — left.val < node.val < right.val। কিন্তু এটা subtree-গভীরে লুকোনো violation ধরে না।
একটা সঠিক brute force: প্রতিটা node-এর জন্য তার পুরো left subtree-র max আর right subtree-র min আলাদা করে বের করে তুলনা করা।
def valid(node):
if node is None: return True
if max(left_subtree) >= node.val: return False
if min(right_subtree) <= node.val: return False
return valid(node.left) and valid(node.right)
10. Why brute force is slow¶
ওই সঠিক brute force-এ প্রতিটা node-এর জন্য তার subtree-গুলোর min/max আলাদা করে পুরো ঘুরে বের করতে হয় — একই subtree বহুবার scan হয়। মোট প্রায় O(n^2) (skewed tree-তে)। মূল অপচয়: subtree-র সীমা বারবার নতুন করে গণনা — অথচ সেই সীমা ancestor থেকে একটা range হিসেবে নিচে passing করলে (06 recursion) এক পাসেই হয়ে যেত।
11. Key observation¶
মূল observation: একটা node বৈধ কিনা তা শুধু তার নিজের child দেখে নয়, তার সব ancestor-এর চাপানো সীমার উপর নির্ভর করে — আর সেই সব সীমা একটামাত্র (low, high) range-এ গুটিয়ে নিচে পাঠানো যায়। left-এ গেলে high কমে, right-এ গেলে low বাড়ে। এই একটা range বহন করলে প্রতিটা node ঠিক একবার দেখেই পুরো গাছ যাচাই — O(n)।
12. Optimized intuition¶
root-কে (-inf, +inf) range দিয়ে শুরু করো। প্রতিটা node-এ চেক করো low < val < high — না মিললে সাথে সাথে False। মিললে দুই child-এ recurse করো সংকুচিত range সহ: left-কে (low, val), right-কে (val, high)। সব node সফল হলে True। প্রতিটা node একবার ছোঁয়া হয়, প্রতি touch O(1) — তাই O(n)।
13. Thinking tweak¶
মোচড়: "প্রতিটা node-এ child-দের সাথে তুলনা করব" ভাবার বদলে ভাবো "প্রতিটা node একটা বৈধ পরিসর নিয়ে নামে — সে কি সেই পরিসরে আছে?" ancestor-দের সব constraint একটা (low, high)-তে গুটিয়ে নিলে গভীরে লুকোনো violation আপনিই ধরা পড়ে, এক পাসেই।
14. Step-by-step dry run¶
[2,1,3] (root 2, left 1, right 3):
check(2, -inf, +inf):-inf < 2 < +inf✓ → দুই child-এ যাওcheck(1, -inf, 2):-inf < 1 < 2✓ → child দুটো None → Truecheck(3, 2, +inf):2 < 3 < +inf✓ → child দুটো None → True- দুই subtree True → root True → return
True
অবৈধ [5,1,4,null,null,3,6]: check(4, 5, +inf)-এ 5 < 4 মিথ্যা → সাথে সাথে False।
15. Python solution¶
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def is_valid_bst(root):
def check(node, low, high):
if node is None:
return True # খালি subtree বৈধ
if not (low < node.val < high): # range-এর বাইরে?
return False
# left-এ high কমে, right-এ low বাড়ে
return check(node.left, low, node.val) and check(node.right, node.val, high)
return check(root, float('-inf'), float('inf'))
# ---- tests ----
# valid: [2,1,3]
assert is_valid_bst(TreeNode(2, TreeNode(1), TreeNode(3))) == True
# invalid: [5,1,4,null,null,3,6]
assert is_valid_bst(TreeNode(5, TreeNode(1),
TreeNode(4, TreeNode(3), TreeNode(6)))) == False
# single node
assert is_valid_bst(TreeNode(1)) == True
# empty tree
assert is_valid_bst(None) == True
# deep violation: [5,4,6,null,null,3,7] -> 3 in right subtree < 5
assert is_valid_bst(TreeNode(5, TreeNode(4),
TreeNode(6, TreeNode(3), TreeNode(7)))) == False
# equal values not allowed: [2,2,2]
assert is_valid_bst(TreeNode(2, TreeNode(2), TreeNode(2))) == False
print("সব test pass!")
16. Line-by-line code explanation¶
class TreeNode— স্ট্যান্ডার্ড binary-tree node (07 trees-এরval/left/right)।def check(node, low, high)— recursive helper; এই node-এর বৈধ পরিসর(low, high)বহন করে (06 recursion-এর extra state)।if node is None: return True— খালি subtree সবসময় বৈধ; recursion-এর base case।if not (low < node.val < high): return False— strict খোলা ব্যবধান চেক; সীমায় বসলেও (==) অবৈধ, তাই duplicate ধরা পড়ে।check(node.left, low, node.val)— left subtree-র সব মান current node-এর চেয়ে ছোট হতে হবে, তাই upper bound হয়node.val।check(node.right, node.val, high)— right subtree-র সব মান চেয়ে বড়, তাই lower bound হয়node.val।- দুই recurse
and— দুই subtree-ই বৈধ হলে তবেই node বৈধ। return check(root, float('-inf'), float('inf'))— root-এর উপর কোনো সীমা নেই, তাই অসীম range দিয়ে শুরু।
17. Output walkthrough¶
[2,1,3]-এ প্রতিটা node তার গড়িয়ে-আসা range-এ পড়ে — return True, assert pass। [5,1,4,3,6]-এ 4 node (5, +inf) range পায়, 5 < 4 মিথ্যা → False (যদিও 4-এর নিজের child 3<4<6 ঠিক ছিল, range invariant deep violation ধরে)। [2,2,2]-এ low < val < high strict বলে সমান মান (2 < 2 মিথ্যা) → False। খালি tree → True। শেষে print: সব test pass!।
18. Time complexity¶
O(n) — প্রতিটা node ঠিক একবার ছোঁয়া হয়, প্রতি touch-এ O(1) range-চেক।
19. Space complexity¶
O(h) — recursion call-stack tree-র উচ্চতা h-র সমান (balanced হলে O(log n), skewed হলে O(n))। কোনো অতিরিক্ত data structure নেই, শুধু stack।
20. Common mistakes¶
- শুধু
node.left.val < node.val < node.right.valচেক করা — deep violation ধরে না (যেমন[5,4,6,3,7]-এর3)। পুরো range বহন করতে হবে। <=/>=ব্যবহার করা strict<-এর বদলে — তখন সমান মান (duplicate) ভুল করে valid হয়; BST-তে সাধারণত strict।- range-কে
node.valদিয়ে update না করে অপরিবর্তিত পাঠানো — তাহলে ancestor constraint নিচে পৌঁছায় না। - in-order traversal করে "sorted কিনা" দেখার সময় আগের মান
-infনা রেখে ভুল init করা (বিকল্প পদ্ধতি; এখানে range-পদ্ধতিই সরল)।
21. Which basic problem this inherits from¶
ভিত্তি: binary tree-র উপর recursive traversal আর BST-property বোঝা (07 trees-এর ../../07-trees/) + প্রতিটা recursive call-এ extra state (low, high) বহন করা (06 recursion-এর ../../06-recursion-and-backtracking/)। এই দুটো cold অবস্থায় জানা থাকলে range-passing validation নিজে থেকেই বেরিয়ে আসে।
22. Similar harder problems¶
- Recover Binary Search Tree (দুটো node ভুল জায়গায়, in-order দিয়ে তাদের খুঁজে swap) — https://leetcode.com/problems/recover-binary-search-tree/
- Kth Smallest Element in a BST (in-order traversal-এর
k-তম) — https://leetcode.com/problems/kth-smallest-element-in-a-bst/ - Binary Search Tree Iterator (controlled in-order, lazy) — https://leetcode.com/problems/binary-search-tree-iterator/
23. Pattern learned¶
Range-passing recursion: প্রতিটা node-কে একটা বৈধ (low, high) ব্যবধান দিয়ে যাচাই করা, আর নিচে নামার সময় ব্যবধান সংকুচিত করা (left-এ high↓, right-এ low↑)। ancestor-দের সব constraint একটা range-এ গুটিয়ে এক পাসে O(n) যাচাই — trees + recursion combo-র canonical রূপ।
24. Final summary¶
ভবিষ্যতের আমাকে: "tree-টা বৈধ BST কিনা" দেখলেই range-passing recursion মনে করবে — root শুরু করো (-inf, +inf) দিয়ে, প্রতি node-এ low < val < high strict চেক, left-এ (low, val), right-এ (val, high)। শুধু সরাসরি child দেখো না — deep violation মিস হবে। strict < রাখো (duplicate trap)। trees-traversal আর state-passing recursion-এর সবচেয়ে পরিষ্কার মেলবন্ধন।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।