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

ข้อ 59 · LC1137 N-th Tribonacci Number (เลข Tribonacci ตัวที่ n) 🟢

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

เหมือน 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

Example 1
Input:
n = 4
Output:
4
Explanation:
T3 = 0 + 1 + 1 = 2, T4 = 1 + 1 + 2 = 4
Example 2
Input:
n = 25
Output:
1389537
Example 3
Input:
n = 0
Output:
0
Explanation:
base case แยกจาก n = 1 และ 2 ซึ่งตอบ 1 ทั้งคู่
Constraints (ข้อจำกัด)
  • 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)

  1. ดัก base case ให้ครบ: n = 0 return 0, n = 1 หรือ 2 return 1
  2. initialize a, b, c = 0, 1, 1 แทน T0, T1, T2
  3. iterate ตั้งแต่ 3 ถึง n: update ทั้งสามตัวพร้อมกันด้วย a, b, c = b, c, a + b + c
  4. 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)abcหมายเหตุ
เริ่ม011T0, T1, T2
i = 3112c = 0+1+1 = 2 (T3)
i = 4124c = 1+1+2 = 4 (T4)
จบ--4คืน c = 4
▶ เฉลยละเอียด (ลองเองก่อนนะ)
เฉลย (Python) — โค้ดนี้รันได้จริงpython
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))  # 1389537
Output
4
1389537

state คือ 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

💡 สรุป pattern

เมื่อ dp[i] depend on ค่าไม่กี่ตัวก่อนหน้าแบบตายตัว ให้ยุบ table เหลือ variable จำนวนคงที่แล้ว rotate ค่า ได้ Space O(1) ทันที — เทคนิคนี้ใช้ได้กับ Fibonacci, Tribonacci และอีกหลายข้อในหมวดนี้