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

การค้นหาแบบทวิภาคในอาร์เรย์ที่หมุนและไม่เรียงลำดับ

แก้โจทย์การค้นหาในอาร์เรย์เรียงลำดับที่ถูกหมุนและการหาค่าน้อยที่สุดในอาร์เรย์ที่ถูกหมุน โดยพิจารณาว่าครึ่งใดเรียงลำดับแล้วในแต่ละขั้น

การค้นหาแบบทวิภาคในอาร์เรย์ที่หมุนและไม่เรียงลำดับ เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

อาร์เรย์เรียงลำดับแบบหมุนคืออะไร

อาร์เรย์เรียงลำดับแบบหมุน คืออาร์เรย์ที่เรียงลำดับแล้ว แต่ถูกตัดที่จุดหมุนบางตำแหน่ง แล้วสลับส่วนทั้งสองเข้าด้วยกัน ตัวอย่างเช่น [4, 5, 6, 7, 0, 1, 2] คืออาร์เรย์เรียงลำดับ [0,1,2,4,5,6,7] ที่หมุนที่ดัชนี 4 การค้นหาแบบทวิภาคมาตรฐานใช้ไม่ได้ในกรณีนี้ เพราะอาร์เรย์ไม่ได้เรียงลำดับทั่วทั้งอาร์เรย์อีกต่อไป

แนวคิดสำคัญคือ อย่างน้อยครึ่งหนึ่งของอาร์เรย์จะเรียงลำดับอยู่เสมอ หลังจากการหมุนทุกแบบ การค้นหาแบบทวิภาคต้องระบุให้ได้ว่าครึ่งใดเรียงลำดับอยู่ ก่อนตัดสินใจว่าจะเลื่อนขอบเขตไปทางใด

# A rotated sorted array — one half is always sorted
arr = [4, 5, 6, 7, 0, 1, 2]
# Left half [4,5,6,7] is sorted
# Right half [0,1,2] is also sorted
# But left[0]=4 > right[-1]=2 => rotation happened in left-to-right crossing

การระบุครึ่งที่เรียงลำดับแล้ว

หลังจากคำนวณ mid แล้ว ให้เปรียบเทียบ arr[lo] กับ arr[mid] หาก arr[lo] <= arr[mid] แสดงว่า ครึ่งซ้ายเรียงลำดับแล้ว มิฉะนั้น ครึ่งขวาเรียงลำดับแล้ว เมื่อทราบว่าครึ่งใดเรียงลำดับแล้ว คุณสามารถตรวจสอบได้ว่าเป้าหมายอยู่ในช่วงที่เรียงลำดับนั้นหรือไม่ แล้วจำกัด search ให้แคบลงตามนั้น

แผนผังการตัดสินใจนี้ทำให้ตัดทิ้งได้พอดีครึ่งหนึ่งของอาร์เรย์ในแต่ละขั้นตอน จึงยังคงมีความซับซ้อน O(log n) แม้จะเป็นอาร์เรย์ที่หมุนแล้ว

def search_rotated(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] == target:
            return mid
        # Left half is sorted
        if nums[lo] <= nums[mid]:
            if nums[lo] <= target < nums[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        # Right half is sorted
        else:
            if nums[mid] < target <= nums[hi]:
                lo = mid + 1
            else:
                hi = mid - 1
    return -1

print(search_rotated([4, 5, 6, 7, 0, 1, 2], 0))  # 4
print(search_rotated([4, 5, 6, 7, 0, 1, 2], 3))  # -1

ติดตามการทำงานผ่านตัวอย่าง

ให้ติดตามการทำงานของ search_rotated([4,5,6,7,0,1,2], 0) ทีละขั้นตอน ในตอนเริ่มต้น lo=0, hi=6, mid=3, arr[mid]=7 เป้าหมาย 0 อยู่ในครึ่งซ้ายที่เรียงลำดับแล้ว [4..7] หรือไม่ ไม่อยู่ ดังนั้นจึงเลื่อน lo=4 จากนั้น lo=4, hi=6, mid=5, arr[mid]=1 ครึ่งซ้าย [0,1] เรียงลำดับแล้ว (arr[lo]=0 <= arr[mid]=1) 0 อยู่ใน [0..1) หรือไม่ อยู่ ดังนั้นจึงกำหนด hi=4 จากนั้น lo=4, hi=4, mid=4, arr[4]=0 — พบที่ดัชนี 4

# Step-by-step trace
nums = [4, 5, 6, 7, 0, 1, 2]
target = 0
steps = []
lo, hi = 0, len(nums) - 1
while lo <= hi:
    mid = lo + (hi - lo) // 2
    steps.append(f'lo={lo} hi={hi} mid={mid} val={nums[mid]}')
    if nums[mid] == target:
        steps.append(f'Found at {mid}')
        break
    if nums[lo] <= nums[mid]:
        if nums[lo] <= target < nums[mid]:
            hi = mid - 1
        else:
            lo = mid + 1
    else:
        if nums[mid] < target <= nums[hi]:
            lo = mid + 1
        else:
            hi = mid - 1
for s in steps:
    print(s)

การจัดการค่าซ้ำในการหมุน

เมื่ออาร์เรย์ที่หมุนแล้วอาจมี ค่าซ้ำ (เช่น [1,3,1,1,1]) เงื่อนไข nums[lo] == nums[mid] จะไม่ชัดเจน — คุณไม่สามารถบอกได้ว่าครึ่งใดเรียงลำดับแล้ว วิธีแก้ที่ปลอดภัยคือเพิ่มค่า lo (หรือลดค่า hi) ทีละหนึ่งแล้วลองใหม่ วิธีนี้ทำให้เวลาในกรณีเลวร้ายที่สุดลดประสิทธิภาพลงเป็น O(n) ซึ่งควรกล่าวถึงแก่ผู้สัมภาษณ์

def search_rotated_with_dups(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] == target:
            return True
        # Ambiguous: shrink left boundary
        if nums[lo] == nums[mid] == nums[hi]:
            lo += 1
            hi -= 1
        elif nums[lo] <= nums[mid]:
            if nums[lo] <= target < nums[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        else:
            if nums[mid] < target <= nums[hi]:
                lo = mid + 1
            else:
                hi = mid - 1
    return False

print(search_rotated_with_dups([1, 3, 1, 1, 1], 3))  # True
print(search_rotated_with_dups([2, 2, 2, 0, 2], 0))  # True

ค้นหาค่าน้อยที่สุดในอาร์เรย์เรียงลำดับที่หมุนแล้ว

ปัญหาที่เกี่ยวข้องคือการหา สมาชิกที่มีค่าน้อยที่สุด ในอาร์เรย์เรียงลำดับที่หมุนแล้ว โดยไม่ต้องค้นหาเป้าหมายที่ระบุ ค่าต่ำสุดจะอยู่ในครึ่งที่ ยังไม่เรียงลำดับ เสมอ ในแต่ละขั้นตอน หาก arr[mid] > arr[hi] ค่าต่ำสุดจะอยู่ในครึ่งขวา (lo = mid + 1) มิฉะนั้นจะอยู่ในครึ่งซ้ายซึ่งรวม mid ไว้ด้วย (hi = mid) เมื่อ lo == hi คุณก็พบค่าต่ำสุดแล้ว

def find_min(nums):
    lo, hi = 0, len(nums) - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] > nums[hi]:
            lo = mid + 1   # min is in right half
        else:
            hi = mid       # min is at mid or left of mid
    return nums[lo]

print(find_min([3, 4, 5, 1, 2]))   # 1
print(find_min([4, 5, 6, 7, 0, 1, 2]))  # 0
print(find_min([11, 13, 15, 17]))  # 11 (no rotation)

เหตุใด arr[lo] <= arr[mid] จึงตรวจจับครึ่งซ้ายที่เรียงลำดับแล้ว

เงื่อนไข arr[lo] <= arr[mid] ใช้ได้เพราะในส่วนย่อยที่เรียงลำดับแล้ว (หรือส่วนที่เรียงลำดับโดยไม่มีการหมุน) สมาชิกตัวแรกจะมีค่าน้อยที่สุดเสมอ หาก arr[lo] <= arr[mid] แสดงว่าไม่มีการหมุนเกิดขึ้นภายใน [lo..mid] ดังนั้นครึ่งนั้นจึงเรียงลำดับแล้ว ความเท่ากันรองรับกรณีที่ lo == mid ซึ่งส่วนย่อยที่มีสมาชิกเพียงหนึ่งตัวถือว่าเรียงลำดับแล้วโดยปริยาย

ในทางกลับกัน หาก arr[lo] > arr[mid] จุดหมุนจะต้องอยู่ระหว่าง lo กับ mid หมายความว่าครึ่งขวา [mid..hi] เป็นส่วนย่อยที่เรียงลำดับต่อเนื่องกัน

# Visualise: detect which half is sorted
examples = [
    ([4, 5, 6, 7, 0, 1, 2], 0, 6),  # mid=3, val=7 => left sorted
    ([6, 7, 0, 1, 2, 4, 5], 0, 6),  # mid=3, val=1 => right sorted
]
for arr, lo, hi in examples:
    mid = lo + (hi - lo) // 2
    if arr[lo] <= arr[mid]:
        print(f'arr[{lo}]={arr[lo]} <= arr[{mid}]={arr[mid]}  => LEFT half sorted')
    else:
        print(f'arr[{lo}]={arr[lo]} >  arr[{mid}]={arr[mid]}  => RIGHT half sorted')

การวิเคราะห์ความซับซ้อน

การค้นหาอาร์เรย์เรียงลำดับที่หมุนแล้วด้วย binary search ยังคงใช้เวลา O(log n) และพื้นที่หน่วยความจำ O(1) เพราะเรายังคงแบ่งพื้นที่การค้นหา (search space) ออกเป็นครึ่งหนึ่งในแต่ละรอบ ความแตกต่างเพียงอย่างเดียวจาก binary search แบบดั้งเดิมคือมีการตรวจสอบเพิ่มเติมที่ใช้เวลาคงที่เพื่อระบุว่าครึ่งใดเรียงลำดับแล้ว

กรณี with ค่าซ้ำ ความซับซ้อนในกรณีเลวร้ายที่สุดจะลดประสิทธิภาพลงเป็น O(n) เพราะเราอาจเพิ่ม lo ได้เพียงครั้งละหนึ่งในแต่ละขั้นตอน ควรกล่าวถึงข้อแลกเปลี่ยนนี้อย่างชัดเจน — แสดงให้เห็นว่าคุณคำนึงถึงกรณีขอบนอกเหนือจากเส้นทางปกติ

การไล่ดู LeetCode 33

LeetCode 33 'ค้นหาในอาร์เรย์เรียงลำดับที่หมุนแล้ว' คือรูปแบบมาตรฐานของปัญหานี้ เงื่อนไขกำหนดให้ ไม่มีค่าซ้ำ และมีการหมุนเพียงครั้งเดียว คำตอบคือฟังก์ชัน search_rotated ที่เราเขียนไว้ก่อนหน้านี้ ประเด็นสำคัญในการสัมภาษณ์ ได้แก่ ระบุสมมติฐานว่าไม่มีค่าซ้ำเสมอ ตรวจสอบอสมการด้วยตัวอย่างจริงที่ขอบเขต และยืนยันว่าดัชนีที่คืนกลับมาถูกต้องทั้งกรณีที่พบและไม่พบเป้าหมาย

# LeetCode 33 — complete solution
def search(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] == target:
            return mid
        if nums[lo] <= nums[mid]:        # left half sorted
            if nums[lo] <= target < nums[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        else:                            # right half sorted
            if nums[mid] < target <= nums[hi]:
                lo = mid + 1
            else:
                hi = mid - 1
    return -1

# Tests
print(search([4,5,6,7,0,1,2], 0))   # 4
print(search([4,5,6,7,0,1,2], 3))   # -1
print(search([1], 0))               # -1

LeetCode 153: หาค่าต่ำสุดโดยไม่มีค่าซ้ำ

LeetCode 153 'หาค่าต่ำสุดในอาร์เรย์เรียงลำดับที่หมุนแล้ว' ให้หาค่าต่ำสุดโดยไม่มีค่าซ้ำ วิธีการคือเปรียบเทียบ arr[mid] กับ arr[hi] (ไม่ใช่ arr[lo]) เพื่อระบุว่าค่าต่ำสุดอยู่ด้านใด หาก arr[mid] > arr[hi] ค่าต่ำสุดจะอยู่ทางขวา มิฉะนั้นจะอยู่ที่ mid หรือทางซ้าย วิธีนี้จะบรรจบเข้าหาค่าต่ำสุดในเวลา O(log n)

def findMin(nums):
    lo, hi = 0, len(nums) - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] > nums[hi]:
            lo = mid + 1
        else:
            hi = mid
    return nums[lo]

print(findMin([3,4,5,1,2]))         # 1
print(findMin([4,5,6,7,0,1,2]))     # 0
print(findMin([11,13,15,17]))       # 11

จำนวนครั้งที่หมุนและดัชนีจุดหมุน

เมื่อคุณสามารถหาสมาชิกที่มีค่าน้อยที่สุดได้แล้ว คุณก็ทราบ จำนวนครั้งที่หมุน ด้วย นั่นคือดัชนีของค่าต่ำสุดจะเท่ากับจำนวนตำแหน่งที่อาร์เรย์ถูกหมุนไปทางขวาพอดี ตัวอย่างเช่น ใน [4,5,6,7,0,1,2] ค่าต่ำสุดอยู่ที่ดัชนี 4 ดังนั้นอาร์เรย์จึงถูกหมุน 4 ตำแหน่ง

เมื่อทราบจุดหมุนแล้ว คุณสามารถใช้ binary search มาตรฐานโดยจัดการดัชนีแบบโมดูโล n: real_idx = (mid + pivot) % n การเขียนในรูปแบบนี้อาจช่วยให้ใช้เหตุผลได้ง่ายขึ้นเมื่อทำงานกับโครงสร้างที่มีการอ้างอิงดัชนีแบบวงกลม

def search_via_pivot(nums, target):
    n = len(nums)
    # Find pivot (index of minimum)
    lo, hi = 0, n - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] > nums[hi]:
            lo = mid + 1
        else:
            hi = mid
    pivot = lo
    # Binary search with offset
    lo, hi = 0, n - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        real_mid = (mid + pivot) % n
        if nums[real_mid] == target:
            return real_mid
        elif nums[real_mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

print(search_via_pivot([4,5,6,7,0,1,2], 0))  # 4

ประกอบทุกส่วนเข้าด้วยกัน

เมื่อพบปัญหาเกี่ยวกับอาร์เรย์ที่หมุนแล้วในการสัมภาษณ์ ให้ทำตามแผนผังการตัดสินใจนี้ ขั้นแรก ให้พิจารณาว่าคุณต้องการ หาเป้าหมาย หรือ หาค่าต่ำสุด หากต้องการหาเป้าหมาย ให้ใช้แนวทางระบุครึ่งที่เรียงลำดับแล้ว หากต้องการหาค่าต่ำสุด ให้เปรียบเทียบ mid กับ hi หากอาจมีค่าซ้ำ ให้กล่าวถึงกรณีเลวร้ายที่สุด O(n) และเพิ่มทางเลือกสำรองสำหรับการหดขอบเขต

ฝึกฝนโดยไล่ดูการทำงานของโค้ดกับตัวอย่างคลาสสิกสามกรณี ได้แก่ ไม่มีการหมุน หมุนหนึ่งครั้ง และหมุนจนค่าต่ำสุดไปอยู่ที่ตำแหน่งสุดท้าย

ตรวจสอบอย่างรวดเร็ว

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า อาร์เรย์เรียงลำดับที่หมุนแล้วจะมีอย่างน้อยหนึ่งครึ่งที่เรียงลำดับแล้วเสมอ ให้เปรียบเทียบ arr[lo] กับ arr[mid] เพื่อระบุว่าครึ่งใดเรียงลำดับแล้ว ก่อนตัดสินใจว่าจะค้นหาที่ใด และ การหาค่าต่ำสุดใช้การเปรียบเทียบ arr[mid] กับ arr[hi] เพื่อระบุจุดหมุนของการหมุน บทถัดไปเราจะสำรวจรูปแบบ binary search สำหรับขอบเขตล่างและขอบเขตบน

คำถามที่พบบ่อย

บทเรียน “การค้นหาแบบทวิภาคในอาร์เรย์ที่หมุนและไม่เรียงลำดับ” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การค้นหาแบบทวิภาคในอาร์เรย์ที่หมุนและไม่เรียงลำดับ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การค้นหาแบบทวิภาคในอาร์เรย์ที่หมุนและไม่เรียงลำดับ”

แก้โจทย์การค้นหาในอาร์เรย์เรียงลำดับที่ถูกหมุนและการหาค่าน้อยที่สุดในอาร์เรย์ที่ถูกหมุน โดยพิจารณาว่าครึ่งใดเรียงลำดับแล้วในแต่ละขั้น คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การค้นหาแบบทวิภาคในอาร์เรย์ที่หมุนและไม่เรียงลำดับ” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม

ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

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

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