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

Dynamic Programming เบื้องต้น

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

จำผลลัพธ์ที่คำนวณแล้วเพื่อไม่คำนวณซ้ำ — เปลี่ยนโจทย์ช้าระเบิดให้เร็วขึ้นมหาศาล

Dynamic Programming (DP) คือเทคนิคแก้ปัญหาที่ "ปัญหาย่อยซ้ำกัน" โดยจำผลที่คำนวณแล้วไว้ ไม่คำนวณซ้ำ ฟังดูยากแต่หัวใจง่ายมาก — และคุณเจอแนวคิดนี้มาแล้วในรูป @lru_cache (บท 3)

ปัญหา: fibonacci แบบ naive ช้าระเบิด

fib แบบ recursion ธรรมดาคำนวณ subproblem เดิมซ้ำเป็นล้านครั้ง — O(2^n)

python
def fib_slow(n):
    if n < 2:
        return n
    return fib_slow(n - 1) + fib_slow(n - 2)
# fib_slow(40) ช้ามาก — คำนวณ fib เดิมซ้ำมหาศาล

วิธีที่ 1: Memoization (top-down)

จำผลที่เคยคำนวณไว้ใน dict (หรือใช้ @lru_cache จากบท 3) — เจอแล้วหยิบเลย ไม่คำนวณซ้ำ → O(n)

python
import functools

@functools.lru_cache(maxsize=None)   # memoize อัตโนมัติ!
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(40))   # เร็วมาก (จำผลไว้แล้ว)

วิธีที่ 2: Tabulation (bottom-up)

สร้างคำตอบจากเล็กไปใหญ่ เก็บใน list/ตัวแปร — ไม่ใช้ recursion

python
def fib_tab(n):
    if n < 2:
        return n
    a, b = 0, 1
    for _ in range(n - 1):
        a, b = b, a + b      # สร้างจากล่างขึ้นบน
    return b

print(fib_tab(40))   # เร็ว O(n) ใช้ memory คงที่

ตัวอย่าง: climbing stairs

ขึ้นบันได n ขั้น ก้าวได้ทีละ 1 หรือ 2 ขั้น มีกี่วิธี — เป็น fibonacci แฝง

python
def climb(n):
    if n <= 2:
        return n
    a, b = 1, 2
    for _ in range(n - 2):
        a, b = b, a + b
    return b

print(climb(5))   # 8 วิธี
เมื่อไรใช้ DP

เห็นโจทย์ที่ "นับจำนวนวิธี", "หาค่ามากสุด/น้อยสุด" ที่แตกเป็นปัญหาย่อยซ้ำกัน → คิดถึง DP เริ่มจากเขียน recursion ให้ถูกก่อน แล้วเติม memoization (@lru_cache) — ได้ DP ทันที นี่คือเหตุผลที่ decorator (บท 1) และ caching (บท 3) เชื่อมมาถึงตรงนี้

สรุปหัวข้อนี้

  • DP = ปัญหาย่อยซ้ำกัน → จำผลไว้ ไม่คำนวณซ้ำ
  • memoization (top-down): recursion + cache (@lru_cache)
  • tabulation (bottom-up): สร้างจากเล็กไปใหญ่ ไม่ใช้ recursion
  • เริ่มจาก recursion ถูกก่อน แล้วเติม cache = ได้ DP
แบบฝึกหัด

1) เทียบเวลา fib_slow กับ fib (lru_cache) ที่ n=35 ด้วย timeit 2) เขียน climbing stairs 3) เขียน fib แบบ tabulation 4) โจทย์ coin change: นับจำนวนวิธีจ่ายเงิน n บาทด้วยเหรียญที่มี