ขอบเขตล่างและขอบเขตบน
สร้าง bisect_left และ bisect_right ตั้งแต่ต้น แล้วประยุกต์ใช้เพื่อหาตำแหน่งแรกและตำแหน่งสุดท้ายของค่าเป้าหมาย
ขอบเขตล่างและขอบเขตบน เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
ขอบเขตล่างและขอบเขตบนคืออะไร
ขอบเขตล่างของค่าเป้าหมายในอาร์เรย์เรียงลำดับ คือดัชนีของสมาชิกตัวแรกที่มีค่ามากกว่าหรือเท่ากับเป้าหมาย (มักเรียกว่า bisect_left) ส่วนขอบเขตบนคือดัชนีของสมาชิกตัวแรกที่มีค่ามากกว่าอย่างเคร่งครัดเป้าหมาย (bisect_right) ทั้งสองขอบเขตจะครอบคลุมการปรากฏทุกครั้งของเป้าหมาย และทำให้สามารถสอบถามช่วงข้อมูลได้ในเวลา O(log n)
การดำเนินการทั้งสองอย่างนี้เป็นพื้นฐานของปัญหาในการสัมภาษณ์หลายประเภท เช่น การนับจำนวนครั้งที่ปรากฏ การหาช่วง ตำแหน่งแทรก และอื่น ๆ
arr = [1, 2, 2, 2, 3, 5]
# lower bound of 2 => index 1 (first element >= 2)
# upper bound of 2 => index 4 (first element > 2)
# occurrences of 2 => upper - lower = 4 - 1 = 3
print('lower bound of 2:', 1)
print('upper bound of 2:', 4)
print('count of 2:', 4 - 1)การนำขอบเขตล่างไปใช้งาน (bisect_left)
bisect_left(arr, x) คืนค่าดัชนีซ้ายสุด i ที่ทำให้ arr[i] >= x หรือคืนค่า len(arr) หากสมาชิกทั้งหมดมีค่าน้อยกว่า การทำงานนี้ใช้ ขอบเขตบนแบบไม่รวมปลาย: hi = len(arr) เงื่อนไขวนซ้ำ lo < hi และปรับค่า hi = mid เมื่อ arr[mid] >= x วิธีนี้ทำให้คำตอบบรรจบเข้าหาตำแหน่งที่ถูกต้องซ้ายสุด
def bisect_left(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid # arr[mid] >= x, so potential answer
return lo # lo == hi == insertion point
arr = [1, 2, 2, 2, 3, 5]
print(bisect_left(arr, 2)) # 1
print(bisect_left(arr, 0)) # 0 (before all)
print(bisect_left(arr, 6)) # 6 (after all)
print(bisect_left(arr, 3)) # 4การนำขอบเขตบนไปใช้งาน (bisect_right)
bisect_right(arr, x) คืนค่าดัชนีซ้ายสุด i ที่ทำให้ arr[i] > x มีเพียงหนึ่งบรรทัดที่แตกต่างจาก bisect_left: เงื่อนไขเปลี่ยนจาก arr[mid] < x เป็น arr[mid] <= x เมื่อ arr[mid] <= x คำตอบจะอยู่ทางขวาของ mid อย่างเคร่งครัด ดังนั้นจึงกำหนด lo = mid + 1 มิฉะนั้นจะจำกัดช่วงจากด้านขวา
def bisect_right(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] <= x:
lo = mid + 1 # arr[mid] <= x, so answer is strictly right
else:
hi = mid
return lo
arr = [1, 2, 2, 2, 3, 5]
print(bisect_right(arr, 2)) # 4
print(bisect_right(arr, 0)) # 0
print(bisect_right(arr, 5)) # 6
print(bisect_right(arr, 4)) # 5นับจำนวนครั้งที่ปรากฏด้วยขอบเขตทั้งสอง
หากต้องการนับจำนวนครั้งที่เป้าหมายปรากฏในอาร์เรย์เรียงลำดับโดยใช้เวลา O(log n) ให้ใช้ขอบเขตทั้งสอง: จำนวน = bisect_right(arr, target) - bisect_left(arr, target) หากจำนวนเป็น 0 แสดงว่าไม่มีเป้าหมายอยู่ วิธีนี้เร็วกว่าไล่ตรวจทีละสมาชิกอย่างมีนัยสำคัญ และเป็นแนวทางมาตรฐานสำหรับการสอบถามความถี่ของข้อมูลที่เรียงลำดับแล้ว
import bisect
def count_occurrences(arr, target):
left = bisect.bisect_left(arr, target)
right = bisect.bisect_right(arr, target)
return right - left
arr = [1, 2, 2, 2, 3, 3, 5]
print(count_occurrences(arr, 2)) # 3
print(count_occurrences(arr, 3)) # 2
print(count_occurrences(arr, 4)) # 0
print(count_occurrences(arr, 1)) # 1หาตำแหน่งแรกและตำแหน่งสุดท้ายของเป้าหมาย
LeetCode 34 'หาตำแหน่งแรกและตำแหน่งสุดท้ายของสมาชิกในอาร์เรย์เรียงลำดับ' ขอให้คุณคืนค่า [first_idx, last_idx] ในเวลา O(log n) ตำแหน่งแรกคือ bisect_left(arr, target) — แต่ต้องตรวจสอบว่า arr[result] == target เท่านั้น ตำแหน่งสุดท้ายคือ bisect_right(arr, target) - 1 หากการตรวจสอบอย่างใดอย่างหนึ่งไม่ผ่าน ให้คืนค่า [-1, -1]
import bisect
def search_range(nums, target):
left = bisect.bisect_left(nums, target)
if left == len(nums) or nums[left] != target:
return [-1, -1]
right = bisect.bisect_right(nums, target) - 1
return [left, right]
print(search_range([5,7,7,8,8,10], 8)) # [3, 4]
print(search_range([5,7,7,8,8,10], 6)) # [-1, -1]
print(search_range([], 0)) # [-1, -1]ตำแหน่งแทรก (LeetCode 35)
LeetCode 35 'ค้นหาตำแหน่งแทรก' ถามว่าเป้าหมายควรถูกแทรกที่ใดเพื่อให้อาร์เรย์ยังคงเรียงลำดับอยู่ คำตอบคือ bisect_left(arr, target) พอดี หากมีเป้าหมายอยู่ bisect_left จะคืนค่าดัชนีของเป้าหมาย หากไม่มีเป้าหมายอยู่ bisect_left จะคืนค่าดัชนีที่ควรแทรกเป้าหมาย ไม่จำเป็นต้องจัดการกรณีพิเศษ — ฟังก์ชันเดียวกันรองรับทั้งสองสถานการณ์
import bisect
def searchInsert(nums, target):
return bisect.bisect_left(nums, target)
print(searchInsert([1,3,5,6], 5)) # 2 (exists at index 2)
print(searchInsert([1,3,5,6], 2)) # 1 (would insert between 1 and 3)
print(searchInsert([1,3,5,6], 7)) # 4 (would append at end)
print(searchInsert([1,3,5,6], 0)) # 0 (would prepend)ความแตกต่างระหว่าง bisect_left กับ bisect_right
เมื่อไม่มีค่าซ้ำ bisect_left และ bisect_right จะคืนค่าดัชนีเดียวกัน ความแตกต่างจะสำคัญก็ต่อเมื่อเป้าหมายปรากฏหลายครั้ง bisect_left ชี้ไปยังสำเนาแรก ส่วน bisect_right ชี้ไปยังตำแหน่งถัดจากสำเนาสุดท้ายหนึ่งตำแหน่ง ให้เลือกใช้โดยพิจารณาว่าต้องการแทรกก่อนสำเนาเดิม (ซ้าย) หรือหลังสำเนาเดิมทั้งหมด (ขวา)
import bisect
arr = [1, 2, 2, 2, 3]
# Insert a new 2 before all existing 2s
print(bisect.bisect_left(arr, 2)) # 1
# Insert a new 2 after all existing 2s
print(bisect.bisect_right(arr, 2)) # 4
# For a value not in array, both give same insertion point
print(bisect.bisect_left(arr, 2.5)) # 4
print(bisect.bisect_right(arr, 2.5)) # 4การใช้ขอบเขตกับการสอบถามความถี่ในช่วงของข้อมูลเรียงลำดับ
เมื่อจำเป็นต้องตอบคำถามเกี่ยวกับความถี่ในช่วงจำนวนมากบนอาร์เรย์เรียงลำดับอย่างมีประสิทธิภาพ ให้เตรียมอาร์เรย์เรียงลำดับไว้ครั้งเดียว แล้วใช้การแบ่งช่วงสำหรับแต่ละคำถาม แต่ละคำถามจะตอบได้ว่า 'มีสมาชิกกี่ตัวอยู่ใน [lo, hi]?' ในเวลา O(log n) แทนที่จะเป็น O(n) รูปแบบนี้พบได้ในปัญหาเกี่ยวกับการนับสมาชิกภายในช่วงค่าหลังจากเรียงลำดับแล้ว
import bisect
def count_in_range(arr, lo, hi):
'''Count elements in arr with lo <= val <= hi. arr must be sorted.'''
left = bisect.bisect_left(arr, lo)
right = bisect.bisect_right(arr, hi)
return right - left
arr = sorted([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5])
print(arr) # [1,1,2,3,3,4,5,5,5,6,9]
print(count_in_range(arr, 3, 5)) # 6 (3,3,4,5,5,5)
print(count_in_range(arr, 1, 2)) # 3 (1,1,2)การทำ binary search ด้วยคีย์แบบกำหนดเอง
บางครั้งคีย์ที่ใช้ค้นหาไม่ใช่ค่าที่จัดเก็บไว้โดยตรง แต่เป็นคุณสมบัติที่คำนวณขึ้นมา โมดูล bisect ของไพธอนไม่รองรับฟังก์ชันคีย์โดยตรง แต่คุณสามารถทำ binary search ด้วยตนเองโดยนำคีย์มาใช้ภายในวงวน รูปแบบนี้พบได้เมื่อค้นหารายการออบเจ็กต์ด้วยแอตทริบิวต์อย่างใดอย่างหนึ่ง
# Binary search on a list of (score, name) tuples by score
def lower_bound_by_score(records, min_score):
lo, hi = 0, len(records)
while lo < hi:
mid = lo + (hi - lo) // 2
if records[mid][0] < min_score:
lo = mid + 1
else:
hi = mid
return lo
records = [(50, 'Alice'), (72, 'Bob'), (72, 'Carol'), (88, 'Dave'), (95, 'Eve')]
idx = lower_bound_by_score(records, 72)
print(idx) # 1 (first record with score >= 72)
print(records[idx:]) # [(72,'Bob'),(72,'Carol'),(88,'Dave'),(95,'Eve')]ข้อผิดพลาดทั่วไปในการสัมภาษณ์เกี่ยวกับขอบเขต
ข้อผิดพลาดที่พบบ่อยที่สุดคือการลืมตรวจสอบหลังเรียกใช้ bisect_left ฟังก์ชันนี้จะคืนค่าดัชนีแทรกที่ถูกต้องเสมอ แต่ไม่ได้รับประกันว่าสมาชิกที่ดัชนีนั้นจะมีค่าเท่ากับเป้าหมาย ให้ตรวจสอบ arr[result] == target เสมอ ก่อนสรุปว่าพบเป้าหมายแล้ว
ข้อผิดพลาดประการที่สองคือการใช้ bisect_right เมื่อต้องการการปรากฏครั้งแรก — bisect_right จะคืนค่าตำแหน่งถัดจากการปรากฏครั้งสุดท้ายหนึ่งตำแหน่ง ดังนั้นการลบ 1 จะให้ตำแหน่งสุดท้าย ไม่ใช่ตำแหน่งแรก
import bisect
arr = [1, 3, 5, 7]
target = 4
# bisect_left returns 2 (insertion point for 4 between 3 and 5)
idx = bisect.bisect_left(arr, target)
print(idx) # 2
# Validate: arr[2] is 5, not 4 => target absent
found = idx < len(arr) and arr[idx] == target
print('Found:', found) # Falseสรุป: ควรใช้ bisect_left หรือ bisect_right เมื่อใด
ใช้ bisect_left เมื่อจำเป็นต้องหา: การปรากฏครั้งแรกของเป้าหมาย จุดแทรกที่เลื่อนสำเนาเดิมไปทางขวา หรือการตรวจสอบว่าเป้าหมายมีอยู่หรือไม่ ใช้ bisect_right เมื่อจำเป็นต้องหา: ตำแหน่งถัดจากการปรากฏครั้งสุดท้ายหนึ่งตำแหน่ง จุดแทรกหลังสำเนาเดิมทั้งหมด หรือจำนวนสมาชิกที่มีค่า <= เป้าหมาย (ซึ่งเท่ากับ bisect_right(arr, target))
ทั้งสองทำงานในเวลา O(log n) และเป็นส่วนหนึ่งของไลบรารีมาตรฐานของไพธอน ดังนั้นคุณจึงสามารถนำเข้าและใช้งานได้โดยตรง เว้นแต่ผู้สัมภาษณ์จะขอให้เขียนขึ้นใหม่ตั้งแต่ต้น
ตรวจสอบอย่างรวดเร็ว
ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า bisect_left ค้นหาสมาชิกตัวแรกที่มีค่า >= เป้าหมาย bisect_right ค้นหาสมาชิกตัวแรกที่มีค่า > เป้าหมาย (ถัดจากการปรากฏครั้งสุดท้ายหนึ่งตำแหน่ง) และ ผลต่างของทั้งสองค่าคือจำนวนครั้งที่ปรากฏในเวลา O(log n) บทถัดไปเราจะสำรวจ binary search บนพื้นที่คำตอบ ซึ่งพื้นที่ search เป็นช่วงของคำตอบที่เป็นไปได้ ไม่ใช่ดัชนีของอาร์เรย์
คำถามที่พบบ่อย
บทเรียน “ขอบเขตล่างและขอบเขตบน” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ขอบเขตล่างและขอบเขตบน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ขอบเขตล่างและขอบเขตบน”
สร้าง bisect_left และ bisect_right ตั้งแต่ต้น แล้วประยุกต์ใช้เพื่อหาตำแหน่งแรกและตำแหน่งสุดท้ายของค่าเป้าหมาย คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “ขอบเขตล่างและขอบเขตบน” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม
ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การค้นหาแบบทวิภาคคลาสสิก: ซ้าย ขวา กลาง
- การค้นหาแบบทวิภาคในอาร์เรย์ที่หมุนและไม่เรียงลำดับ
- ขอบเขตล่างและขอบเขตบน
- การค้นหาแบบทวิภาคบนช่วงคำตอบ