การค้นหาแบบทวิภาคบนช่วงคำตอบ
มองช่วงคำตอบต่อเนื่องเป็นพื้นที่ค้นหา เพื่อแก้โจทย์อย่างเวลาต่ำสุดในการทำงานให้เสร็จและความจุในการขนส่งพัสดุ
การค้นหาแบบทวิภาคบนช่วงคำตอบ เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA 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) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การค้นหาแบบทวิภาคบนช่วงคำตอบ”
มองช่วงคำตอบต่อเนื่องเป็นพื้นที่ค้นหา เพื่อแก้โจทย์อย่างเวลาต่ำสุดในการทำงานให้เสร็จและความจุในการขนส่งพัสดุ คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “การค้นหาแบบทวิภาคบนช่วงคำตอบ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม
ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การค้นหาแบบทวิภาคคลาสสิก: ซ้าย ขวา กลาง
- การค้นหาแบบทวิภาคในอาร์เรย์ที่หมุนและไม่เรียงลำดับ
- ขอบเขตล่างและขอบเขตบน
- การค้นหาแบบทวิภาคบนช่วงคำตอบ