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

การค้นหาแบบทวิภาคบนช่วงคำตอบ

มองช่วงคำตอบต่อเนื่องเป็นพื้นที่ค้นหา เพื่อแก้โจทย์อย่างเวลาต่ำสุดในการทำงานให้เสร็จและความจุในการขนส่งพัสดุ

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

การทำ binary search บนพื้นที่คำตอบ

คนส่วนใหญ่รู้จัก binary search สำหรับค้นหาค่าในอาร์เรย์เรียงลำดับ แต่ binary search จะทรงพลังยิ่งขึ้นเมื่อใช้กับพื้นที่ของคำตอบที่เป็นไปได้ แทนที่จะค้นหาในอาร์เรย์ คุณจะค้นหาในช่วงตัวเลข — ตัวอย่างเช่น 'จำนวนวันขั้นต่ำที่ใช้จัดส่งพัสดุทั้งหมดคือเท่าใด' — แล้วใช้ฟังก์ชันตรวจสอบเพื่อตัดสินว่าคำตอบตัวเลือกหนึ่งเป็นไปได้หรือไม่

เทคนิคนี้เปลี่ยนปัญหาการหาค่าที่เหมาะที่สุดจำนวนมากจาก O(n²) หรือแย่กว่านั้น ให้เหลือ O(n log(max_answer))

แม่แบบพื้นที่คำตอบ

แม่แบบนี้มีองค์ประกอบสามส่วน ขั้นแรก กำหนดช่วง search [lo, hi] ที่ครอบคลุมคำตอบที่ถูกต้องทั้งหมด ขั้นที่สอง เขียนการตรวจสอบความเป็นไปได้ can_achieve(mid) ซึ่งคืนค่า True หากค่า mid สามารถทำให้เกิดขึ้นได้ ขั้นที่สาม ทำ binary search บน [lo, hi] หาก can_achieve(mid) เป็นจริง ให้ขยับไปหาคำตอบที่เล็กลง (หรือใหญ่ขึ้น) มิฉะนั้นให้ขยับไปในทิศทางตรงกันข้าม

คุณสมบัติสำคัญคือ ฟังก์ชันตรวจสอบความเป็นไปได้ต้องเป็นโมโนโทน — เมื่อคำตอบหนึ่งเป็นไปได้แล้ว ค่าทั้งหมดที่อยู่ถัดจากค่านั้นก็เป็นไปได้เช่นกัน (หรือค่าทั้งหมดที่อยู่ก่อนหน้านั้นเป็นไปไม่ได้)

# Generic template
def answer_space_search(lo, hi, is_feasible):
    result = hi  # or lo, depending on direction
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if is_feasible(mid):
            result = mid
            hi = mid - 1   # try to minimise further
        else:
            lo = mid + 1
    return result

ตัวอย่าง: ความจุในการจัดส่งพัสดุ

LeetCode 1011 'ความจุในการจัดส่งพัสดุภายใน D วัน': กำหนดรายการน้ำหนักและจำนวน D วัน ให้หาความจุการจัดส่งขั้นต่ำที่ใช้จัดส่งพัสดุทั้งหมดตามลำดับภายใน D วัน คำตอบอยู่ในช่วง [max(weights), sum(weights)] ความจุหนึ่งค่าจะถือว่าเป็นไปได้ หากการจำลองแบบละโมบสามารถจัดส่งพัสดุทั้งหมดได้ภายใน D วัน การค้นหาแบบทวิภาคบนช่วงความจุจะใช้เวลา O(n log(sum))

def shipWithinDays(weights, days):
    def can_ship(capacity):
        needed_days, current_load = 1, 0
        for w in weights:
            if current_load + w > capacity:
                needed_days += 1
                current_load = 0
            current_load += w
        return needed_days <= days

    lo, hi = max(weights), sum(weights)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if can_ship(mid):
            hi = mid        # feasible, try smaller
        else:
            lo = mid + 1    # not feasible, need more capacity
    return lo

print(shipWithinDays([1,2,3,4,5,6,7,8,9,10], 5))  # 15
print(shipWithinDays([3,2,2,4,1,4], 3))            # 6

ตัวอย่าง: โคโคกินกล้วย

LeetCode 875 'โคโคกินกล้วย': โคโคกินกล้วยได้ K ผลต่อชั่วโมง และต้องการกินกล้วย H กองให้หมดภายในเวลา H ชั่วโมงพอดี โดยทำให้ K ต่ำที่สุด ช่วงการค้นหาคือ [1, max(piles)] การตรวจสอบมีดังนี้: ที่อัตรา K จำนวนชั่วโมงทั้งหมดคือผลรวมของ ceil(จำนวนกล้วยในกอง/K) ซึ่งต้อง <= H เราจะค้นหาแบบทวิภาคเพื่อหา K ค่าต่ำสุดที่ทำให้เงื่อนไขนี้เป็นจริง

import math

def minEatingSpeed(piles, h):
    def can_finish(k):
        return sum(math.ceil(p / k) for p in piles) <= h

    lo, hi = 1, max(piles)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if can_finish(mid):
            hi = mid      # feasible, try lower speed
        else:
            lo = mid + 1  # too slow
    return lo

print(minEatingSpeed([3,6,7,11], 8))    # 4
print(minEatingSpeed([30,11,23,4,20], 5))  # 30

ตัวอย่าง: จำนวนวันขั้นต่ำในการจัดช่อดอกไม้

LeetCode 1482 'จำนวนวันขั้นต่ำในการจัดช่อดอกไม้ m ช่อ': คุณต้องจัดช่อดอกไม้ m ช่อ โดยแต่ละช่อใช้ดอกไม้ที่บานติดต่อกัน k ดอก ดอกไม้ดอกที่ i จะบานในวันที่ bloomDay[i] ให้ค้นหาแบบทวิภาคบนวัน โดยช่วงคือ [1, max(bloomDay)] การตรวจสอบความเป็นไปได้จะนับดอกไม้ที่บานติดต่อกันและดูว่าสามารถจัดดอกไม้เป็น m ช่อได้หรือไม่ สมบัติแบบโมโนโทนคือ หากวันที่ d ใช้งานได้ วันที่ d+1 ก็ใช้งานได้เช่นกัน

def minDays(bloomDay, m, k):
    if m * k > len(bloomDay):
        return -1  # impossible

    def can_make(day):
        bouquets = consecutive = 0
        for bd in bloomDay:
            if bd <= day:
                consecutive += 1
                if consecutive == k:
                    bouquets += 1
                    consecutive = 0
            else:
                consecutive = 0
        return bouquets >= m

    lo, hi = 1, max(bloomDay)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if can_make(mid):
            hi = mid
        else:
            lo = mid + 1
    return lo

print(minDays([1,10,3,10,2], 3, 1))  # 3
print(minDays([1,10,3,10,2], 3, 2))  # -1

การระบุช่วงการค้นหา

การเลือกช่วง [lo, hi] ที่ถูกต้องมีความสำคัญอย่างยิ่ง lo ควรเป็นคำตอบที่เป็นไปได้ต่ำที่สุด (เช่น สมาชิกที่มีค่าน้อยที่สุด, 1 หรือ 0) และ hi ควรเป็นคำตอบที่เป็นไปได้สูงที่สุด (เช่น ผลรวมของสมาชิกทั้งหมด, สมาชิกที่มีค่าสูงที่สุด หรือ n) หากกำหนด hi เล็กเกินไป คำตอบที่ถูกต้องบางส่วนจะถูกตัดออก แต่หากกำหนดให้ใหญ่เกินไปก็ไม่เป็นไร เพราะการค้นหาแบบทวิภาคจะยังลู่เข้าได้ภายใน O(log(hi - lo)) ขั้นตอน

# Choosing lo and hi for common problems:
# Capacity to ship: lo=max(weights), hi=sum(weights)
# Koko eating:      lo=1,            hi=max(piles)
# Square root:      lo=1,            hi=x
# Allocate books:   lo=max(pages),   hi=sum(pages)

def isqrt_bs(x):
    if x < 2:
        return x
    lo, hi = 1, x
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if mid * mid <= x:
            lo = mid + 1
        else:
            hi = mid
    return lo - 1

for n in [0, 1, 4, 8, 9, 15, 16]:
    print(f'isqrt({n}) = {isqrt_bs(n)}')

หาค่าสูงสุดเทียบกับหาค่าต่ำสุด: ทิศทางมีความสำคัญ

การค้นหาแบบทวิภาคบนปริภูมิคำตอบมีสองรูปแบบ หาคำตอบที่ต่ำที่สุด: เมื่อการตรวจสอบผ่าน ให้ลองค่าที่เล็กลง (hi = mid) และเมื่อไม่ผ่าน ให้ลองค่าที่ใหญ่ขึ้น (lo = mid + 1) หาคำตอบที่สูงที่สุด: เมื่อการตรวจสอบผ่าน ให้ลองค่าที่ใหญ่ขึ้น (lo = mid + 1 โดยบันทึก mid ไว้เป็นตัวเลือก) และเมื่อไม่ผ่าน ให้ลองค่าที่เล็กลง (hi = mid - 1) ก่อนเขียนโค้ด ควรระบุให้ชัดเจนเสมอว่ากำลังค้นหาในทิศทางใด

# Maximise: largest x such that f(x) is feasible
def max_feasible(lo, hi, is_feasible):
    result = lo - 1   # sentinel: no feasible answer found
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if is_feasible(mid):
            result = mid
            lo = mid + 1  # try larger
        else:
            hi = mid - 1
    return result

# Example: largest k such that k^2 <= 50
print(max_feasible(1, 50, lambda k: k * k <= 50))  # 7

จัดสรรหน้าขั้นต่ำ (ปัญหาคลาสสิก)

กำหนดหนังสือ n เล่มซึ่งมีจำนวนหน้าอยู่ในอาร์เรย์ และนักเรียน k คน ให้จัดสรรหนังสือเป็นช่วงต่อเนื่องเพื่อให้จำนวนน้อยที่สุดของหน้าที่นักเรียนซึ่งได้รับหน้ามากที่สุดต้องอ่าน ใช้การค้นหาแบบทวิภาคบนคำตอบ (ค่าสูงสุดที่เป็นไปได้ต่ำที่สุด) การตรวจสอบความเป็นไปได้จะจัดสรรหนังสือให้กับนักเรียนแบบละโมบ: เมื่อการเพิ่มหนังสือจะทำให้เกินค่าสูงสุดปัจจุบัน ให้จัดสรรหนังสือนั้นให้นักเรียนคนใหม่ หากจำนวนนักเรียนที่ต้องใช้ <= k ค่าสูงสุดนั้นก็เป็นค่าที่ทำได้

def allocate_min_pages(pages, k):
    if k > len(pages):
        return -1

    def is_feasible(max_pages):
        students, current = 1, 0
        for p in pages:
            if p > max_pages:
                return False  # single book exceeds limit
            if current + p > max_pages:
                students += 1
                current = 0
            current += p
        return students <= k

    lo, hi = max(pages), sum(pages)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if is_feasible(mid):
            hi = mid
        else:
            lo = mid + 1
    return lo

print(allocate_min_pages([12, 34, 67, 90], 2))  # 113
print(allocate_min_pages([10, 20, 30, 40], 2))  # 60

การวิเคราะห์ความซับซ้อนของการค้นหาบนปริภูมิคำตอบ

ความซับซ้อนด้านเวลาคือ O(n × log(range)) โดย n คือค่าใช้จ่ายของการตรวจสอบความเป็นไปได้ (โดยปกติคือการสแกนเชิงเส้น) และช่วง = hi - lo (ขนาดของปริภูมิคำตอบ) ตัวอย่างเช่น หากผลรวมของจำนวนหน้าคือ 10⁹ และการตรวจสอบความเป็นไปได้มีความซับซ้อน O(n) เวลารวมคือ O(n log 10⁹) ≈ O(30n) ซึ่งดีกว่าการลองทุกกรณีแบบ O(n²) อย่างมาก

ความซับซ้อนด้านพื้นที่คือ O(1) สำหรับการค้นหาแบบทวิภาคเอง และเพิ่มพื้นที่ที่การตรวจสอบความเป็นไปได้ใช้

import math

# Compare brute force vs answer-space binary search
# For sum = 10^9 and n = 10^5:
brute_ops = 10**9         # try every possible answer
bsearch_ops = 10**5 * math.log2(10**9)  # n * log(range)
print(f'Brute force: {brute_ops:,.0f} operations')
print(f'Binary search: {bsearch_ops:,.0f} operations')
print(f'Speedup: {brute_ops / bsearch_ops:,.0f}x')

สมาชิกที่น้อยที่สุดลำดับที่ k ในเมทริกซ์เรียงลำดับ

LeetCode 378 'สมาชิกที่น้อยที่สุดลำดับที่ k ในเมทริกซ์เรียงลำดับ': แต่ละแถวและคอลัมน์ของเมทริกซ์ขนาด n×n เรียงลำดับแล้ว ให้ค้นหาแบบทวิภาคบนค่าคำตอบในช่วงตั้งแต่สมาชิกมุมซ้ายบนถึงสมาชิกมุมขวาล่างของเมทริกซ์ การตรวจสอบความเป็นไปได้จะนับสมาชิกที่มีค่า <= ค่ากลาง โดยใช้ตัวชี้ที่เริ่มจากมุมล่างซ้าย และทำงานในเวลา O(n) ให้หาค่าที่น้อยที่สุดซึ่งมีสมาชิกอย่างน้อย k ตัวที่มีค่า <= ค่ากลาง

def kthSmallest(matrix, k):
    n = len(matrix)

    def count_le(mid):
        count, row, col = 0, n - 1, 0
        while row >= 0 and col < n:
            if matrix[row][col] <= mid:
                count += row + 1
                col += 1
            else:
                row -= 1
        return count

    lo, hi = matrix[0][0], matrix[n-1][n-1]
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if count_le(mid) >= k:
            hi = mid
        else:
            lo = mid + 1
    return lo

matrix = [[1,5,9],[10,11,13],[12,13,15]]
print(kthSmallest(matrix, 8))  # 13

การสังเกตปัญหาปริภูมิคำตอบ

ปัญหาที่เหมาะกับการค้นหาแบบทวิภาคบนปริภูมิคำตอบมักมีสัญญาณร่วมกัน ได้แก่ คำถามต้องการค่าต่ำสุดหรือค่าสูงสุด คำตอบอยู่ในช่วงตัวเลขที่มีขอบเขต และการเพิ่ม (หรือลด) คำตอบที่เป็นตัวเลือกทำให้ความเป็นไปได้ดีขึ้นหรือแย่ลงอย่างเป็นลำดับเดียว คำสำคัญที่พบบ่อย ได้แก่ 'ค่าสูงสุดที่เป็นไปได้ต่ำที่สุด' 'การดำเนินการไม่เกิน k ครั้ง' และ 'ภายใน d วัน'

เมื่อพบสัญญาณเหล่านี้ ให้กำหนด lo และ hi ทันที เขียนฟังก์ชันตรวจสอบความเป็นไปได้ แล้วใช้รูปแบบมาตรฐาน วิธีที่มีโครงสร้างนี้แทบไม่ล้มเหลวในการสัมภาษณ์

ตรวจสอบความเข้าใจ

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

สรุปบทเรียน

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

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

บทเรียน “การค้นหาแบบทวิภาคบนช่วงคำตอบ” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การค้นหาแบบทวิภาคบนช่วงคำตอบ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ 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 ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การค้นหาแบบทวิภาคบนช่วงคำตอบ” ใช้เวลานานแค่ไหน

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

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

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

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

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