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

ข้อ 60 · LC746 Min Cost Climbing Stairs (ขึ้นบันไดถูกสุด) 🟢

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

choose ก้าว 1 หรือ 2 ขั้นให้พ้นบันไดโดยจ่าย cost รวม minimum (น้อยที่สุด) ด้วย DP แบบ Fibonacci

โจทย์ (LC746): กำหนด array จำนวนเต็ม cost มา โดย cost[i] คือค่าใช้จ่ายของขั้นบันไดที่ i เมื่อจ่ายค่าใช้จ่ายของขั้นนั้นแล้ว สามารถก้าวขึ้นได้ 1 หรือ 2 ขั้น สามารถเริ่มต้นได้จากขั้นที่ index 0 หรือ index 1 ก็ได้ ให้หาค่าใช้จ่าย minimum ที่จะไปให้ถึงยอดบันได (จุดที่พ้นขั้นสุดท้าย)

Example 1
Input:
cost = [10, 15, 20]
Output:
15
Explanation:
เริ่มที่ขั้น index 1 จ่าย 15 แล้วก้าว 2 ขั้นพ้นยอดบันไดพอดี
Example 2
Input:
cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
Output:
6
Explanation:
เริ่มที่ขั้น index 0 แล้วก้าวทีละ 2 ขั้นไปเรื่อย ๆ (0→2→4→6) จ่าย 1 บาทสี่ครั้ง จากนั้นก้าว 1 ขั้นไป index 7 (จ่าย 1 บาท) แล้วก้าว 2 ขั้นพ้นยอด (จ่าย 1 บาท) รวมเป็น 6
Constraints (ข้อจำกัด)
  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999

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

นี่คือ DP 1 มิติ pattern เดียวกับ Fibonacci เป๊ะ ต่างแค่ใช้ min แทนการ sum นิยาม dp[i] = cost น้อยสุดที่จะมาถึงขั้น i ก่อนเหยียบ การจะถึงขั้น i ได้ ต้อง jump (กระโดด) มาจากขั้น i-1 (จ่าย cost[i-1]) หรือขั้น i-2 (จ่าย cost[i-2]) เลือกทางที่ถูกกว่า

ประเด็นที่คนงงคือ cost อยู่ที่ตัวขั้น ไม่ใช่ที่การ step และเราจ่ายเมื่อออกจากขั้นนั้น การ define dp[i] เป็นค่าที่จะมาถึงขั้น i (ยังไม่เหยียบ i) ทำให้ transition สวย และเป้าหมายคือ dp[n] เมื่อ n คือ length ของ cost (จุดพ้นยอดซึ่งไม่มีค่า cost)

  1. initialize dp ยาว n+1 โดย n = len(cost)
  2. ตั้ง base case: dp[0] = dp[1] = 0 เพราะเริ่มที่ขั้น 0 หรือ 1 ได้ฟรี
  3. iterate i จาก 2 ถึง n: dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
  4. return dp[n] ซึ่งคือจุดพ้นบันได
จุดพลาดที่พบบ่อย

อย่า return dp[n-1] เพราะนั่นคือค่ามาถึงขั้นสุดท้าย ไม่ใช่จุดพ้นบันได ต้อง return dp[n] และระวังว่าค่า cost ที่ sum คือ cost[i-1] / cost[i-2] (ขั้นที่ jump ออกมา) ไม่ใช่ cost[i]

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

ไล่ cost = [10,15,20] (n = 3):

idp[i-1]+cost[i-1]dp[i-2]+cost[i-2]dp[i]
0--0 (base)
1--0 (base)
20+15 = 150+10 = 1010
310+20 = 300+15 = 1515 ← คำตอบ
▶ เฉลยละเอียด (ลองเองก่อนนะ)
เฉลย (Python) — โค้ดนี้รันได้จริงpython
def min_cost_climbing_stairs(cost):
    n = len(cost)
    dp = [0] * (n + 1)   # dp[i] = ค่าใช้จ่ายน้อยสุดเพื่อมาถึงขั้น i
    # dp[0] = dp[1] = 0 เพราะเริ่มที่ขั้น 0 หรือ 1 ได้ฟรี
    for i in range(2, n + 1):
        dp[i] = min(dp[i - 1] + cost[i - 1],   # มาจากขั้นก่อนหน้า
                    dp[i - 2] + cost[i - 2])   # มาจากขั้นก่อนสองขั้น
    return dp[n]

print(min_cost_climbing_stairs([10, 15, 20]))                # 15
print(min_cost_climbing_stairs([1,100,1,1,1,100,1,1,100,1])) # 6
Output
15
6

การ define dp[i] เป็นค่าที่จะมาถึงขั้น i (ยังไม่เหยียบ i) ทำให้ transition สวย: จะยืนที่ขั้น i ได้ ต้อง jump มาจาก i-1 หรือ i-2 ซึ่งต้องจ่าย cost ของขั้นที่ jump ออกมา (cost[i-1] หรือ cost[i-2]) เป้าหมาย dp[n] คือจุดที่พ้นบันได (เหนือขั้นสุดท้าย) จึงไม่มีค่า cost

เพราะเริ่มที่ขั้น 0 หรือ 1 ได้ฟรี เราจึงตั้ง dp[0] = dp[1] = 0 เป็น base case แล้วไล่เติมจาก i = 2 ขึ้นไป โจทย์นี้คือ pattern เดียวกับ Fibonacci เป๊ะ ต่างแค่ใช้ min แทนการ sum ถ้าอยากประหยัด Space ยุบเหลือสอง variable แทน table ได้เช่นเดียวกับข้อ Tribonacci

Time O(n) iterate table รอบเดียว · Space O(n) จาก array dp (ลดเหลือ O(1) ได้ด้วย variable สองตัวแทน table)

💡 สรุป pattern

โจทย์ minimize/maximize ที่แต่ละ state ต่อยอดจากไม่กี่ state ก่อนหน้า ใช้โครง Fibonacci ได้เลย เพียงเปลี่ยน operator (ตัวดำเนินการ) จาก sum → min/max และ define state ให้ตรงกับสิ่งที่ต้องการวัด