On this page
ข้อ 60 · LC746 Min Cost Climbing Stairs (ขึ้นบันไดถูกสุด) 🟢
choose ก้าว 1 หรือ 2 ขั้นให้พ้นบันไดโดยจ่าย cost รวม minimum (น้อยที่สุด) ด้วย DP แบบ Fibonacci
โจทย์ (LC746): กำหนด array จำนวนเต็ม cost มา โดย cost[i] คือค่าใช้จ่ายของขั้นบันไดที่ i เมื่อจ่ายค่าใช้จ่ายของขั้นนั้นแล้ว สามารถก้าวขึ้นได้ 1 หรือ 2 ขั้น สามารถเริ่มต้นได้จากขั้นที่ index 0 หรือ index 1 ก็ได้ ให้หาค่าใช้จ่าย minimum ที่จะไปให้ถึงยอดบันได (จุดที่พ้นขั้นสุดท้าย)
- Input:
- cost = [10, 15, 20]
- Output:
- 15
- Explanation:
- เริ่มที่ขั้น index 1 จ่าย 15 แล้วก้าว 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
- 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)
- initialize dp ยาว n+1 โดย n = len(cost)
- ตั้ง base case: dp[0] = dp[1] = 0 เพราะเริ่มที่ขั้น 0 หรือ 1 ได้ฟรี
- iterate i จาก 2 ถึง n: dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
- 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):
| i | dp[i-1]+cost[i-1] | dp[i-2]+cost[i-2] | dp[i] |
|---|---|---|---|
| 0 | - | - | 0 (base) |
| 1 | - | - | 0 (base) |
| 2 | 0+15 = 15 | 0+10 = 10 | 10 |
| 3 | 10+20 = 30 | 0+15 = 15 | 15 ← คำตอบ |
▶ เฉลยละเอียด (ลองเองก่อนนะ)
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])) # 615
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)
โจทย์ minimize/maximize ที่แต่ละ state ต่อยอดจากไม่กี่ state ก่อนหน้า ใช้โครง Fibonacci ได้เลย เพียงเปลี่ยน operator (ตัวดำเนินการ) จาก sum → min/max และ define state ให้ตรงกับสิ่งที่ต้องการวัด