0Pricing
Coding Interview Prep · บทเรียน

ตรวจสอบ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การแทรกและค้นหาใน BST
  2. การลบใน BST: สามกรณี
  3. ตรวจสอบ BST และคุณสมบัติลำดับกลาง
  4. ค่าที่น้อยที่สุดลำดับ k ผลรวมช่วง และ BST เป็นอาร์เรย์เรียงลำดับ
← กลับไปที่ Coding Interview Prep