ตรวจสอบ BST และคุณสมบัติลำดับกลาง
ตรวจสอบต้นไม้ทวิภาคว่าเป็น BST หรือไม่ด้วยขอบเขตค่าต่ำสุดและสูงสุดที่ส่งต่อลงไปในต้นไม้ และตรวจว่าการท่องลำดับกลางให้ลำดับที่เรียงแล้ว
ตรวจสอบ BST และคุณสมบัติลำดับกลาง เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
ปัญหาการตรวจสอบความถูกต้องของ BST
การตรวจสอบความถูกต้องของ BST (LeetCode #98) เป็นโจทย์สัมภาษณ์ยอดนิยมที่ทำให้ผู้สมัครจำนวนมากพลาด วิธีตรงไปตรงมาคือ ตรวจสอบเพียงว่าค่าของแต่ละโหนดมากกว่าโหนดลูกด้านซ้ายและน้อยกว่าโหนดลูกด้านขวา แต่ การตรวจสอบเฉพาะที่นี้ไม่เพียงพอ โหนดในต้นไม้ย่อยอาจเป็นไปตามกฎเฉพาะที่ แต่ละเมิดคุณสมบัติ BST โดยรวมได้ วิธีแก้ที่ถูกต้องคือส่งต่อ ขอบเขตค่าต่ำสุดและค่าสูงสุด ลงไปตามต้นไม้
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# Why local check fails:
# 5
# / \
# 1 4
# / \
# 3 6
# Node 4's children (3, 6) satisfy local rule,
# but 4 < 5 and is in the RIGHT subtree -- BST violated!
print('Local check is insufficient -- use min/max bounds')แนวทางขอบเขตค่าต่ำสุดและค่าสูงสุด
ส่งต่อ ขอบเขตล่างและขอบเขตบน ไปตามการเรียกซ้ำ ในแต่ละโหนด ให้ตรวจสอบว่า low < node.val < high เมื่อเรียกซ้ำไปทางซ้าย ให้อัปเดตขอบเขตบนเป็น node.val เพราะต้นไม้ย่อยด้านซ้ายต้องมีค่าน้อยกว่า เมื่อเรียกซ้ำไปทางขวา ให้อัปเดตขอบเขตล่างเป็น node.val เพราะต้นไม้ย่อยด้านขวาต้องมีค่ามากกว่า เริ่มต้นด้วย low = -infinity และ high = +infinity
def is_valid_bst(root, low=float('-inf'), high=float('inf')):
if not root:
return True
if not (low < root.val < high):
return False
return (is_valid_bst(root.left, low, root.val) and
is_valid_bst(root.right, root.val, high))
# Valid BST:
valid = TreeNode(5)
valid.left = TreeNode(3)
valid.right = TreeNode(7)
print(is_valid_bst(valid)) # True
# Invalid BST (3 is in wrong subtree conceptually):
invalid = TreeNode(5)
invalid.left = TreeNode(1)
invalid.right = TreeNode(4)
invalid.right.left = TreeNode(3)
invalid.right.right = TreeNode(6)
print(is_valid_bst(invalid)) # False (4 < 5 in right subtree)การตรวจสอบด้วยการท่องแบบอินออร์เดอร์
อีกแนวทางหนึ่งในการตรวจสอบใช้ คุณสมบัติการเรียงลำดับแบบอินออร์เดอร์ของ BST โดยเก็บลำดับอินออร์เดอร์ไว้ แล้วตรวจสอบว่าลำดับดังกล่าวเพิ่มขึ้นอย่างเคร่งครัดหรือไม่ วิธีนี้กระชับและเข้าใจได้ง่าย อย่างไรก็ตาม ต้องใช้พื้นที่เพิ่มเติม O(n) เพื่อเก็บลำดับ วิธีที่ปรับให้มีประสิทธิภาพใช้ตัวชี้ prev เพียงตัวเดียวระหว่างการท่องต้นไม้ เพื่อตรวจสอบแต่ละคู่โดยไม่ต้องเก็บลำดับทั้งหมด
def is_valid_bst_inorder(root):
prev = [float('-inf')]
def inorder(node):
if not node:
return True
if not inorder(node.left):
return False
if node.val <= prev[0]: # not strictly increasing
return False
prev[0] = node.val
return inorder(node.right)
return inorder(root)
valid = TreeNode(5)
valid.left = TreeNode(3)
valid.right = TreeNode(7)
valid.left.left = TreeNode(1)
valid.left.right = TreeNode(4)
print(is_valid_bst_inorder(valid)) # True
invalid = TreeNode(5)
invalid.left = TreeNode(6) # 6 > 5 in left subtree!
print(is_valid_bst_inorder(invalid)) # Falseเปรียบเทียบแนวทางการตรวจสอบทั้งสองแบบ
แนวทางขอบเขตค่าต่ำสุดและค่าสูงสุด ใช้เวลา O(n) และพื้นที่ O(h) โดยเก็บไว้เพียงขอบเขตบนสแตกการเรียกใช้ แนวทางใช้ตัวชี้ค่าก่อนหน้าแบบอินออร์เดอร์ ก็ใช้เวลา O(n) และพื้นที่ O(h) เช่นกัน ทั้งสองแนวทางมีประสิทธิภาพสูงสุด แนวทางขอบเขตค่าต่ำสุดและค่าสูงสุดมีความยืดหยุ่นกว่า และทำงานได้อย่างเป็นระเบียบเมื่อต่อยอดไปยังโจทย์ที่มีเงื่อนไขเพิ่มเติม ในการสัมภาษณ์ ควรพร้อมนำเสนอทั้งสองแนวทางและพูดคุยถึงข้อแลกเปลี่ยน การแสดงให้เห็นว่าคุณตระหนักถึงทางเลือกต่าง ๆ เป็นสัญญาณที่ดี
# Both approaches:
# Time: O(n) -- visit each node once
# Space: O(h) -- call stack depth
# h = O(log n) balanced, O(n) skewed
# When to choose which:
# min/max bounds:
# - Cleaner for trees with constraints beyond BST
# - No global state (purely functional)
# in-order prev:
# - More intuitive (sorted sequence check)
# - Easier to convert to iterative with a stack
print('Both O(n) time, O(h) space -- choose by clarity')กู้คืน BST: โหนดสองโหนดที่สลับกัน
การกู้คืน BST (LeetCode #99) เป็นการซ่อมแซม BST ที่มีโหนดสองโหนดสลับตำแหน่งกันพอดี ระหว่างการท่องต้นไม้แบบอินออร์เดอร์ BST ที่เรียงถูกต้องจะให้ลำดับที่เรียงแล้ว หากมีโหนดสองโหนดสลับกัน จะพบ การละเมิดหนึ่งหรือสองจุด ซึ่งเป็นจุดที่ prev.val > current.val โหนดแรกของการละเมิดครั้งแรกและโหนดที่สองของการละเมิดครั้งสุดท้ายคือโหนดที่อยู่ผิดตำแหน่งสองโหนดนั้น ให้สลับค่าของโหนดทั้งสอง
def recover_tree(root):
first = second = prev = None
def inorder(node):
nonlocal first, second, prev
if not node:
return
inorder(node.left)
if prev and prev.val > node.val:
if not first:
first = prev # first violator
second = node # always update second
prev = node
inorder(node.right)
inorder(root)
# Swap values of the two misplaced nodes
if first and second:
first.val, second.val = second.val, first.val
root = TreeNode(3)
root.left = TreeNode(1)
root.right = TreeNode(4)
root.right.left = TreeNode(2) # 2 and 3 are swapped
recover_tree(root)
print(root.val, root.right.left.val) # 2, 3 (fixed)แปลง BST แบบอินออร์เดอร์เป็นอาร์เรย์เรียงลำดับ
การแปลง BST เป็นอาร์เรย์เรียงลำดับทำได้โดยตรง เพียงท่องต้นไม้แบบอินออร์เดอร์และเก็บค่าไว้ การดำเนินการนี้ใช้เวลา O(n) และพื้นที่ O(n) และเป็นวิธีที่รวดเร็วในการนำอัลกอริทึมสำหรับอาร์เรย์เรียงลำดับ เช่น การค้นหาแบบทวิภาคและตัวชี้สองตัว มาใช้กับข้อมูล BST โดยมักเป็นขั้นตอนเริ่มต้นของโจทย์ BST หลายส่วน เช่น 'ผสาน BST สองต้น' หรือ 'ค้นหาค่ามัธยฐานของ BST'
def bst_to_sorted_array(root):
result = []
def inorder(node):
if not node:
return
inorder(node.left)
result.append(node.val)
inorder(node.right)
inorder(root)
return result
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(6)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right.left = TreeNode(5)
root.right.right = TreeNode(7)
print(bst_to_sorted_array(root)) # [1, 2, 3, 4, 5, 6, 7]ผสาน BST สองต้น
หากต้องการ ผสาน BST สองต้น ให้เป็นอาร์เรย์เรียงลำดับ ให้แปลงแต่ละต้นเป็นอาร์เรย์เรียงลำดับโดยใช้เวลา O(n) และ O(m) จากนั้นผสานอาร์เรย์เรียงลำดับทั้งสองด้วยขั้นตอนการผสานของการเรียงลำดับแบบผสาน โดยใช้เวลา O(n+m) เวลารวมคือ O(n+m) หากต้องการผลลัพธ์เป็น BST ที่สมดุล ให้นำอาร์เรย์เรียงลำดับที่ผสานแล้วไปป้อนให้อัลกอริทึมแปลงอาร์เรย์เรียงลำดับเป็น BST การแยกโจทย์ออกเป็นปัญหาย่อยง่าย ๆ เช่นนี้เป็นลักษณะของวิธีแก้ที่ชัดเจนและเข้าใจง่ายสำหรับผู้สัมภาษณ์
def merge_two_bsts(root1, root2):
def inorder(node, arr):
if not node:
return
inorder(node.left, arr)
arr.append(node.val)
inorder(node.right, arr)
arr1, arr2 = [], []
inorder(root1, arr1)
inorder(root2, arr2)
# Merge two sorted arrays
merged = []
i = j = 0
while i < len(arr1) and j < len(arr2):
if arr1[i] <= arr2[j]:
merged.append(arr1[i]); i += 1
else:
merged.append(arr2[j]); j += 1
merged.extend(arr1[i:])
merged.extend(arr2[j:])
return merged
r1 = TreeNode(2); r1.left = TreeNode(1); r1.right = TreeNode(4)
r2 = TreeNode(3); r2.left = TreeNode(0); r2.right = TreeNode(5)
print(merge_two_bsts(r1, r2)) # [0, 1, 2, 3, 4, 5]นับโหนดในช่วงของ BST
นับจำนวนโหนดที่มีค่าอยู่ในช่วง [low, high] การสแกนอินออร์เดอร์แบบตรวจสอบทุกโหนดใช้เวลา O(n) ส่วนวิธีที่ใช้ความรู้เกี่ยวกับ BST จะตัดกิ่งที่ไม่จำเป็นออก หากค่าของโหนดปัจจุบันน้อยกว่าขอบเขตล่าง ก็ไม่จำเป็นต้องตรวจสอบต้นไม้ย่อยด้านซ้าย เพราะค่าทั้งหมดในต้นไม้ย่อยนั้นก็น้อยกว่าขอบเขตล่างเช่นกัน ในทำนองเดียวกัน ให้ตัดต้นไม้ย่อยด้านขวาออกเมื่อค่าปัจจุบันมากกว่าขอบเขตบน กรณีโดยเฉลี่ยใช้เวลา O(log n + k) โดย k คือจำนวนโหนดที่ตรงตามเงื่อนไข
def range_sum_bst(root, low, high):
if not root:
return 0
total = 0
if low <= root.val <= high:
total += root.val
if root.val > low: # left subtree may have values >= low
total += range_sum_bst(root.left, low, high)
if root.val < high: # right subtree may have values <= high
total += range_sum_bst(root.right, low, high)
return total
root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(15)
root.left.left = TreeNode(3)
root.left.right = TreeNode(7)
root.right.right = TreeNode(18)
print(range_sum_bst(root, 7, 15)) # 7 + 10 + 15 = 32ค่าซ้ำและ BST แบบเคร่งครัดเทียบกับไม่เคร่งครัด
เงื่อนไขคงที่มาตรฐานของ BST ใช้ อสมการแบบเคร่งครัด โดยค่าของต้นไม้ย่อยด้านซ้ายต้องน้อยกว่าอย่างเคร่งครัด และค่าของต้นไม้ย่อยด้านขวาต้องมากกว่าอย่างเคร่งครัด โจทย์บางข้ออนุญาตให้มีค่าซ้ำ โดยวางค่าเหล่านั้นไว้ในต้นไม้ย่อยด้านซ้าย (ซ้าย <= ราก) หรือต้นไม้ย่อยด้านขวา (ราก < ขวา) เมื่อตรวจสอบ BST ให้ตรวจดูคำจำกัดความในโจทย์เสมอ แนวทางขอบเขตค่าต่ำสุดและค่าสูงสุดรองรับทั้งสองรูปแบบ โดยปรับว่าการตรวจสอบขอบเขตจะใช้เงื่อนไขแบบเคร่งครัดหรือแบบรวมขอบเขต
# Strict BST (LeetCode default): left < root < right
def is_valid_strict(root, lo=float('-inf'), hi=float('inf')):
if not root:
return True
if not (lo < root.val < hi): # STRICT inequalities
return False
return (is_valid_strict(root.left, lo, root.val) and
is_valid_strict(root.right, root.val, hi))
# Non-strict BST (allows duplicates in right): left <= root < right
def is_valid_nonstrict(root, lo=float('-inf'), hi=float('inf')):
if not root:
return True
if not (lo <= root.val < hi): # NOTE: <= for left side
return False
return (is_valid_nonstrict(root.left, lo, root.val + 1) and
is_valid_nonstrict(root.right, root.val, hi))
print('Always clarify strict vs non-strict with interviewer')อินออร์เดอร์ในฐานะเครื่องมือสารพัดประโยชน์สำหรับ BST
การท่องต้นไม้แบบอินออร์เดอร์เป็นเครื่องมือสารพัดประโยชน์สำหรับโจทย์ BST เมื่อใดก็ตามที่โจทย์ BST ถามเกี่ยวกับลำดับที่เรียงแล้ว องค์ประกอบลำดับที่ k การค้นหาตามช่วง หรือคุณสมบัติของลำดับ ให้พิจารณาว่าการสแกนแบบอินออร์เดอร์หรือการสแกนย้อนกลับจะให้คำตอบได้หรือไม่ โจทย์เฉพาะของ BST ส่วนใหญ่ลดรูปเหลือเพียง ท่องต้นไม้ตามลำดับที่เรียงแล้ว และทำบางอย่างในแต่ละขั้น การมองเห็นความสัมพันธ์นี้ได้อย่างรวดเร็วเป็นทักษะสำคัญในการสัมภาษณ์
# Problems solved elegantly with in-order:
# 1. Validate BST: check prev <= curr during in-order
# 2. Kth smallest: count k steps in in-order
# 3. Kth largest: count k steps in REVERSE in-order
# 4. Closest value to target: find crossover in in-order
# 5. BST to sorted array: collect in-order into list
# 6. Recover BST: find 1-2 violations in in-order
# 7. Sum of range [lo, hi]: accumulate during in-order
# The key insight: in-order visits BST nodes in sorted order.
# All sorted-order reasoning translates to in-order DFS.
print('In-order = sorted access = foundation of BST reasoning')ค่าที่ใกล้เคียงที่สุดใน BST
ค้นหาโหนดที่มีค่าใกล้เคียงกับค่าเป้าหมายที่กำหนดมากที่สุด ใช้ลำดับของ BST โดยเริ่มจากโหนดราก เก็บค่าที่ใกล้เคียงที่สุดที่พบไว้ และเคลื่อนไปยังทิศทางของค่าเป้าหมาย หากเป้าหมายมีค่าน้อยกว่าให้ไปทางซ้าย หากมากกว่าให้ไปทางขวา วิธีนี้ใช้เวลา O(h) ซึ่งมีประสิทธิภาพมากกว่าการสแกนแบบอินออร์เดอร์ และแสดงให้เห็นถึงการใช้คุณสมบัติของ BST เพื่อตัดพื้นที่ค้นหาที่ไม่จำเป็นออกอย่างมีประสิทธิภาพ
def closest_value(root, target):
closest = root.val
curr = root
while curr:
if abs(curr.val - target) < abs(closest - target):
closest = curr.val
if target < curr.val:
curr = curr.left
elif target > curr.val:
curr = curr.right
else:
break # exact match
return closest
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(5)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
print(closest_value(root, 3.714286)) # 4ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสำหรับการสัมภาษณ์ด้านการเขียนโปรแกรมจากบทเรียนนี้
ทบทวนบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ การตรวจสอบ BST ด้วยขอบเขตค่าต่ำสุดและค่าสูงสุด ซึ่งช่วยหลีกเลี่ยงข้อผิดพลาดจากการตรวจสอบเฉพาะที่ รวมถึง ทางเลือกใช้ตัวชี้ค่าก่อนหน้าแบบอินออร์เดอร์ สำหรับการตรวจสอบ และการใช้อินออร์เดอร์เป็นเครื่องมือหลักของ BST สำหรับผลรวมตามช่วง ค่าที่ใกล้เคียงที่สุด และการผสานข้อมูล บทถัดไป เราจะใช้คุณสมบัติอินออร์เดอร์ของ BST เพื่อค้นหาองค์ประกอบลำดับที่ k ที่น้อยที่สุด
คำถามที่พบบ่อย
บทเรียน “ตรวจสอบ BST และคุณสมบัติลำดับกลาง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ตรวจสอบ BST และคุณสมบัติลำดับกลาง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ตรวจสอบ BST และคุณสมบัติลำดับกลาง”
ตรวจสอบต้นไม้ทวิภาคว่าเป็น BST หรือไม่ด้วยขอบเขตค่าต่ำสุดและสูงสุดที่ส่งต่อลงไปในต้นไม้ และตรวจว่าการท่องลำดับกลางให้ลำดับที่เรียงแล้ว คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “ตรวจสอบ BST และคุณสมบัติลำดับกลาง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม
ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การแทรกและค้นหาใน BST
- การลบใน BST: สามกรณี
- ตรวจสอบ BST และคุณสมบัติลำดับกลาง
- ค่าที่น้อยที่สุดลำดับ k ผลรวมช่วง และ BST เป็นอาร์เรย์เรียงลำดับ