On this page
ข้อ 59 · LC1137 N-th Tribonacci Number (เลข Tribonacci ตัวที่ n) 🟢
เหมือน Fibonacci แต่ sum สามตัวก่อนหน้า ฝึกยุบ DP table ให้เหลือ Space O(1)
โจทย์ (LC1137): Tribonacci sequence นิยามว่า T0 = 0, T1 = 1, T2 = 1 และ Tn+3 = Tn + Tn+1 + Tn+2 สำหรับ n >= 0 กำหนดจำนวนเต็ม n มา ให้ return ค่า Tn
- Input:
- n = 4
- Output:
- 4
- Explanation:
- T3 = 0 + 1 + 1 = 2, T4 = 1 + 1 + 2 = 4
- Input:
- n = 25
- Output:
- 1389537
- Input:
- n = 0
- Output:
- 0
- Explanation:
- base case แยกจาก n = 1 และ 2 ซึ่งตอบ 1 ทั้งคู่
- 0 <= n <= 37
- โจทย์รับประกันว่า คำตอบ อยู่ในช่วงจำนวนเต็ม 32 บิต คือไม่เกิน 2^31 - 1 (ไม่ได้รับประกันค่าระหว่างทาง)
แนวทาง — ต้องใช้อะไร & คิดยังไง
โจทย์นี้คือ DP 1 มิติแบบตำราเลย state คือ dp[i] = ค่า Tribonacci ตัวที่ i และ transition คือ dp[i] = dp[i-1] + dp[i-2] + dp[i-3] ถ้าเขียนตามนิยามด้วย recursion ตรง ๆ จะช้าเป็น O(3^n) เพราะเกิด overlapping subproblems เหมือน Fibonacci เป๊ะ
เราแก้ด้วย bottom-up ได้ แต่เพราะแต่ละตัว depend on แค่สามตัวก่อนหน้าเท่านั้น จึงไม่ต้องเก็บทั้ง array ใช้ variable สามตัว (a, b, c) rotate ไปข้างหน้าก็พอ ประหยัด Space เหลือ O(1)
- ดัก base case ให้ครบ: n = 0 return 0, n = 1 หรือ 2 return 1
- initialize a, b, c = 0, 1, 1 แทน T0, T1, T2
- iterate ตั้งแต่ 3 ถึง n: update ทั้งสามตัวพร้อมกันด้วย a, b, c = b, c, a + b + c
- return c ซึ่งเป็นค่า Tn ล่าสุด
base case ต้องแยกให้ถูก: n = 0 ตอบ 0 ส่วน n = 1 กับ n = 2 ตอบ 1 ทั้งคู่ ถ้ารวบเป็น n < 2 แบบ Fibonacci จะได้ T2 ผิด (กลายเป็น 2 แทนที่จะเป็น 1) และการ assign พร้อมกันในบรรทัดเดียวสำคัญมาก ถ้าแยกเป็นสามบรรทัดจะ overwrite (ทับค่า) กันเอง
ไล่ทีละสเต็ป
ไล่ n = 4 ดูค่า a, b, c rotate ไปแต่ละรอบ:
| รอบ (i) | a | b | c | หมายเหตุ |
|---|---|---|---|---|
| เริ่ม | 0 | 1 | 1 | T0, T1, T2 |
| i = 3 | 1 | 1 | 2 | c = 0+1+1 = 2 (T3) |
| i = 4 | 1 | 2 | 4 | c = 1+1+2 = 4 (T4) |
| จบ | - | - | 4 | คืน c = 4 |
▶ เฉลยละเอียด (ลองเองก่อนนะ)
def tribonacci(n):
if n == 0:
return 0
if n <= 2: # T1 และ T2 เท่ากับ 1
return 1
a, b, c = 0, 1, 1 # T0, T1, T2
for _ in range(3, n + 1):
a, b, c = b, c, a + b + c # เลื่อนหน้าต่างสามตัวไปข้างหน้า
return c
print(tribonacci(4)) # 4
print(tribonacci(25)) # 13895374
1389537state คือ dp[i] = ค่า Tribonacci ตัวที่ i และ transition คือ dp[i] = dp[i-1] + dp[i-2] + dp[i-3] แต่เพราะแต่ละตัว depend on แค่สามตัวก่อนหน้า เราเลยไม่ต้องเก็บ array ทั้งก้อน ใช้ variable a, b, c แทน dp[i-3], dp[i-2], dp[i-1] แล้วเลื่อนไปข้างหน้าทีละก้าวด้วยการ assign พร้อมกัน (a, b, c = b, c, a+b+c)
ทำไมต้อง assign พร้อมกันบรรทัดเดียว? เพราะฝั่งขวาของ = จะถูก evaluate (คำนวณ) ให้เสร็จก่อนทั้งหมด แล้วค่อยจ่ายให้ฝั่งซ้าย ถ้าแยกเป็น a = b แล้ว b = c ทีละบรรทัด ค่า a เดิมจะถูก overwrite ก่อนที่เราจะได้ใช้ ทำให้ a + b + c ผิด
Time O(n) iterate รอบเดียวจาก 3 ถึง n · Space O(1) ใช้แค่สาม variable ไม่โตตาม n
เมื่อ dp[i] depend on ค่าไม่กี่ตัวก่อนหน้าแบบตายตัว ให้ยุบ table เหลือ variable จำนวนคงที่แล้ว rotate ค่า ได้ Space O(1) ทันที — เทคนิคนี้ใช้ได้กับ Fibonacci, Tribonacci และอีกหลายข้อในหมวดนี้