Notes & software courses · Free to learn
Aph's Blog
On this page

ข้อ 53 · LC374 Guess Number Higher or Lower (ทายเลขสูงต่ำ) 🟢

👋 อ่านฟรีทั้งหมดบน Aph's Blog — เนื้อหาภาษาไทย ทำตามทีละหน้าใน sidebar ได้เลย หากมีข้อเสนอแนะหรืออยากให้เพิ่มหัวข้อไหน บอกได้เสมอ

binary search แบบตำราเป๊ะ ๆ เพียงเปลี่ยนจาก compare (เทียบ) ค่าใน array เป็นถาม API guess() ว่าควรไปซ้ายหรือขวา

โจทย์ (LC374): กำลังเล่นเกม Guess Game ที่มีการสุ่มเลขจำนวนเต็มหนึ่งตัวไว้ล่วงหน้าในช่วง 1 ถึง n ทุกครั้งที่เดา (guess) ผิด เกมจะบอกว่าตัวเลขที่สุ่มไว้มากกว่าหรือน้อยกว่าตัวที่เดา ให้เรียกใช้ API ที่มีให้อยู่แล้วคือ int guess(int num) ซึ่ง return -1 ถ้า num ที่เดามากกว่าตัวเลขที่สุ่มไว้ (เดาสูงไป), return 1 ถ้า num น้อยกว่า (เดาต่ำไป), และ return 0 ถ้าเดาถูก ให้ return ตัวเลขที่ระบบสุ่มไว้

Example 1
Input:
n = 10, pick = 6
Output:
6
Explanation:
ทายด้วย binary search: mid = 5 ได้ guess(5) = 1 (น้อยไป), mid = 8 ได้ guess(8) = -1 (มากไป), mid = 6 ได้ guess(6) = 0 (ถูก) จึงคืน 6
Example 2
Input:
n = 1, pick = 1
Output:
1
Explanation:
ช่วงมีตัวเดียวคือ 1 guess(1) คืน 0 ทันที
Example 3
Input:
n = 2, pick = 1
Output:
1
Explanation:
mid = (1+2)//2 = 1 guess(1) คืน 0 ทันทีตั้งแต่ก้าวแรก
Constraints (ข้อจำกัด)
  • 1 <= n <= 2^31 - 1
  • 1 <= pick <= n
  • n ใหญ่ระดับสองพันล้าน → ไล่ทีละเลขไม่ได้เด็ดขาด ต้องตัดครึ่ง

แนวทาง — ต้องใช้อะไร & คิดยังไง

โครงสร้างที่ใช้: binary search พื้นฐานตรง ๆ answer space (ช่วงคำตอบ) sorted อยู่แล้ว (1 ถึง n) และ guess() ทำหน้าที่เหมือนการ compare nums[mid] กับ target แค่มันบอก direction (ทิศทาง) ให้เราแทน

คิดแบบง่าย/ช้าก่อน: ถ้า guess ไล่จาก 1, 2, 3, ... จะเป็น O(n) ซึ่งช้ามากเมื่อ n ใหญ่ แต่เพราะ guess บอก direction ได้ เรา halve (ตัดครึ่ง) ช่วงที่เป็นไปได้ทุกครั้ง เหลือ O(log n)

  1. initialize lo = 1, hi = n
  2. ระหว่าง lo <= hi: compute mid แล้วเรียก res = guess(mid)
  3. ถ้า res == 0 ทายถูก return mid
  4. ถ้า res < 0 (mid มากไป) เลขจริงอยู่ครึ่งซ้าย ตั้ง hi = mid - 1; ถ้า res > 0 (mid น้อยไป) ตั้ง lo = mid + 1
จุดพลาดที่พบบ่อย

อย่าเผลอสลับทิศ res < 0 หมายถึงเลขที่เราทาย มากเกินไป ดังนั้นเลขจริงอยู่ทางซ้าย ต้องขยับ hi ลง ไม่ใช่ขยับ lo ขึ้น

ไล่ทีละสเต็ป

จำลอง n = 10, เลขจริง = 6:

lohimidguess(mid)ทำอะไรต่อ
11051 (น้อยไป)lo = 6
6108-1 (มากไป)hi = 7
6760 (ถูก)คืน 6
▶ เฉลยละเอียด (ลองเองก่อนนะ)
เฉลย (Python) — โค้ดนี้รันได้จริงpython
# LeetCode ให้ API guess() มาให้แล้ว ที่จำลองไว้ตรงนี้เพื่อให้บล็อกนี้รันได้เองทั้งก้อน
SECRET = 6

def guess(num):
    if num > SECRET:
        return -1          # ทายมากไป
    if num < SECRET:
        return 1           # ทายน้อยไป
    return 0               # ทายถูก


# กำหนดให้มี API guess(num) อยู่แล้ว:
#   guess(num) -> -1 ถ้า num มากไป, 1 ถ้า num น้อยไป, 0 ถ้าถูก

def guessNumber(n):
    lo, hi = 1, n
    while lo <= hi:
        mid = (lo + hi) // 2
        res = guess(mid)
        if res == 0:
            return mid          # ทายถูก
        elif res < 0:
            hi = mid - 1        # mid มากไป เลขจริงอยู่ครึ่งซ้าย
        else:
            lo = mid + 1        # mid น้อยไป เลขจริงอยู่ครึ่งขวา
    return -1  # ไม่ควรมาถึงตรงนี้

print(guessNumber(10))     # เลขลับคือ 6
Output
6

โจทย์นี้คือ binary search แบบตำราเป๊ะ ๆ เพียงแต่แทนที่จะ compare กับค่าใน array เราถามผลจาก function guess ที่คอยบอก direction ค่าที่ guess return มามีสามกรณี: 0 คือถูก, negative (ลบ) คือทายมากไป (ต้องลด hi), positive (บวก) คือทายน้อยไป (ต้องเพิ่ม lo)

จุดสำคัญคืออย่าเผลอสลับทิศ res < 0 หมายถึงเลขที่เราทายมากเกินไป ดังนั้นเลขจริงอยู่ทางซ้าย ต้องขยับ hi ลง ถ้าสลับ condition สองอันนี้จะ halve ผิดข้างและหาไม่เจอ

Time O(log n) halve ช่วงทุกก้าว · Space O(1) ใช้ตัวแปรไม่กี่ตัว ไม่มีโครงสร้างเสริม

💡 สรุป pattern

binary search ไม่จำเป็นต้องมี array จริง ขอแค่มีช่วงที่ sorted และมีวิธีบอก direction (compare ค่า/เรียก API/check condition) ว่าคำตอบอยู่ครึ่งไหน ก็ halve ได้แล้ว