Problem overview
The function tribonacci(n: Int) returns T(n), the n-th Tribonacci number. The sequence starts with T(0) = 0, T(1) = 1 and T(2) = 1, and every later term is the sum of the three terms before it: T(n) = T(n - 1) + T(n - 2) + T(n - 3). The sequence begins 0, 1, 1, 2, 4, 7, 13, 24, 44.
For n = 4 the answer is 4, because T(4) = T(3) + T(2) + T(1) = 2 + 1 + 1. For n = 5 the answer is 7. The statement asks for O(n) time and O(1) space.
Approach
This is the Fibonacci idea with a window of three values instead of two. Each new term depends only on the three before it, so the algorithm never needs to remember earlier terms. Three variables hold the latest three terms. On each pass, add them to get the next term and slide the window forward by one place.
The variables are named by age. curr is the newest term, prev is the one before it, and prev2 is the one before that. They start at T(0), T(1) and T(2) (that is, prev2 = 0, prev = 1, curr = 1), so the first term the loop must compute is T(3).
Step by step
- Base cases. If
n < 2, returnn, sinceT(0) = 0andT(1) = 1. - Set up the window. Declare
prev2 = 0,prev = 1andcurr = 1withvar, one per line. All three change later. - Loop from 3 to
n.for (i in 3..n)computesT(3)up toT(n), one per pass. Forn = 2the range is empty, so the loop doesn't run andcurr, which already holdsT(2), is returned. - Compute the next term.
val next = prev2 + prev + currisT(i). - Shift the window, oldest first.
prev2 = prev, thenprev = curr, thencurr = next. Each variable is overwritten only after its old value has been copied one place back. - Return
curr. After the last pass it holdsT(n).
Tracing n = 5: the window starts as 0, 1, 1. At i = 3, next = 2 and the window becomes 1, 1, 2. At i = 4, next = 4 and the window becomes 1, 2, 4. At i = 5, next = 7 and the window becomes 2, 4, 7. The function returns 7.
Why the loop is correct
At the start of the pass for i, prev2, prev and curr hold T(i - 3), T(i - 2) and T(i - 1). Before the first pass (i = 3) these are T(0), T(1) and T(2), which are the starting values. Each pass computes T(i) and shifts the window so the same statement holds for i + 1. When the loop is done, curr is T(n). This is also why the loop can start at 3 and why the case n = 2 needs no special code: the window already ends with T(2).
The solution in Kotlin
class Solution {
fun tribonacci(n: Int): Int {
if (n < 2) {
return n
}
var prev2 = 0
var prev = 1
var curr = 1
for (i in 3..n) {
val next = prev2 + prev + curr
prev2 = prev
prev = curr
curr = next
}
return curr
}
}
Kotlin details:
- Empty ranges are fine.
3..nwithnless than 3 contains no numbers, so the loop body is skipped without an error. The call withn = 2relies on this. varandval. The window variables are reassigned, so they arevar.nextis created anew on each pass and never reassigned, sovalis enough.Intoverflow. AnIntwraps silently past 2147483647.tribonacci(37)returns2082876103, the last value that fits.tribonacci(38)returns-463960867, which is wrong. UseLongwhenncan be larger.- Naming.
nextisn't a reserved word in Kotlin, so it is a valid variable name.
Complexity
Time: O(n). The loop runs n - 2 times with a constant amount of work in each pass.
Space: O(1). Four variables are enough, whatever n is. Storing the whole sequence in a list would take O(n) space, and a plain recursive definition would make an exponential number of calls.
From two variables to three
Compared with Fibonacci, only two things change: the window holds three values instead of two, and the loop starts at 3 instead of 2, because three starting values are known instead of two. The sliding pattern is the same, so you can extend it to any fixed number of previous terms. Each extra term adds one variable and one more assignment in the shifting block. The shifting always goes oldest first, so no value is overwritten before it is copied.
Because Int overflows early, a version with Long variables (0L, 1L, 1L) is worth knowing: it gives 3831006429 for T(38), where the Int version wraps to a negative number.
Edge cases and common mistakes
n = 0,1and2. The first two return through the base case. Forn = 2the loop is skipped andcurris 1. Writing the base case asn < 3withreturn nwould be wrong, because it would give 2 forT(2), which is 1.- Wrong shift order. Setting
curr = nextbefore the other two assignments loses the oldcurr, andprevwould take the new value instead of the old one. - Starting the loop at 2. That repeats the step for
T(2), which is already known, and every result after it comes out too large. - Returning
next.nextonly exists inside the loop, so this won't compile, and it would be missing when the loop is skipped. Usecurr. - Overflow. Past
n = 37, the result no longer fits anInt.
Testing
n = 0gives0,n = 1gives1andn = 2gives1.n = 3gives2.n = 4gives4.n = 5gives7.n = 10gives149.