Problem overview
The function countBinaryStrings receives two non-negative integers, n and k, with k <= n <= 30. It returns how many binary strings of length n contain no run of more than k ones in a row. Only the count is needed, not the strings.
For n = 4 and k = 2 there are 16 binary strings in total. Three of them have a run of three or more ones: 0111, 1110 and 1111. The other 13 are valid, so the answer is 13.
Approach: count strings by how they end
The brute-force way is to generate all 2^n strings and check each one. With n = 30 that is over a billion strings, which is far too slow. Because only the number of strings matters, there is a faster way, based on one observation: whether a string can be extended depends only on how many ones it currently ends with.
So define dp[i][j] as the number of valid strings of length i that end in exactly j consecutive ones. A string can grow by one character in two ways:
- Append a 0. The run of ones is broken, so the new string ends in 0 ones. This is allowed for every
j. - Append a 1. A string ending in
jones now ends inj + 1ones. This is allowed only whenj < k, because a run ofk + 1ones would break the limit.
The table starts with the empty string: one valid string of length 0, ending in 0 ones, so dp[0][0] = 1. Every other string is built from it by appending characters. The answer is the sum of the last row, dp[n][0] through dp[n][k], because a valid string of length n may end in any number of ones from 0 to k.
For n = 4 and k = 2, the rows of the table (columns j = 0, 1, 2) are:
i=0: 1 0 0
i=1: 1 1 0
i=2: 2 1 1
i=3: 4 2 1
i=4: 7 4 2
The last row sums to 7 + 4 + 2 = 13.
Step by step
- Create the table.
dphasn + 1rows (lengths 0 ton) andk + 1columns (0 toktrailing ones). Kotlin fills it with zeros. - Record the empty string with
dp[0][0] = 1. - Loop over the lengths
ifrom 0 ton - 1. Each pass pushes the counts of lengthiforward into rowi + 1, so the last row written is rown. - Loop over the trailing-ones counts
jfrom 0 tok, includingk. - Append a 0: add
dp[i][j]todp[i + 1][0]. It uses+=, because everyjcontributes to the same cell. - Append a 1: if
j < k, adddp[i][j]todp[i + 1][j + 1]. - Sum the last row. Declare
resultas avarstarting at 0, after the nested loops, and adddp[n][j]for eachjfrom 0 tok. - Return
result.
The solution in Kotlin
class Solution {
fun countBinaryStrings(n: Int, k: Int): Int {
val dp = Array(n + 1) { IntArray(k + 1) }
dp[0][0] = 1
for (i in 0 until n) {
for (j in 0..k) {
dp[i + 1][0] += dp[i][j]
if (j < k) {
dp[i + 1][j + 1] += dp[i][j]
}
}
}
var result = 0
for (j in 0..k) {
result += dp[n][j]
}
return result
}
}
The Kotlin details worth knowing:
- Building the table.
Array(n + 1) { IntArray(k + 1) }calls the lambda once per row, and each call creates a newIntArrayofk + 1zeros. Every row is a separate array. Repeating a single row object would make all rows share the same numbers. - Range bounds.
0 until nexcludesn, while0..kincludesk. The two loops use them deliberately:imust stop atn - 1because the body writes into rowi + 1, whilejmust reachkbecause a string can end in exactlykones. +=on an array element.dp[i + 1][0] += dp[i][j]reads the cell, adds, and stores the result back. It is shorthand fordp[i + 1][0] = dp[i + 1][0] + dp[i][j].val dpwith changing contents.valstopsdpfrom being pointed at a different table; it doesn't stop the numbers in it from changing.Intis enough. It holds values up to 2147483647. Forn = 30the largest possible answer is 2^30 = 1073741824, reached whenk = 30, so no sum overflows within the limits. Largernwould needLong.
Complexity
Time: O(n * k). The nested loops run n times k + 1 times, with constant work in each pass. The final sum adds O(k).
Space: O(n * k). The table has (n + 1) * (k + 1) cells. Row i + 1 depends only on row i, so two rows would be enough to cut the space to O(k). This lesson keeps the whole table, which makes the structure easier to see and check.
Edge cases and common mistakes
k = 0. No ones are allowed, so exactly one string qualifies, all zeros.countBinaryStrings(3, 0)returns 1.k = n. Every string is valid, so the answer is 2^n.countBinaryStrings(3, 3)returns 8.n = 0. The empty string is the only string, and the function returns 1.k = 1. No two ones may touch.countBinaryStrings(4, 1)returns 8.- Using
=instead of+=for the appended 0. Eachjwould overwrite the previous contribution, and only the last would count. - Dropping the
j < kguard. The code would write todp[i + 1][k + 1], which is outside the row, and throw anArrayIndexOutOfBoundsException. - Looping
iup ton. The last pass would write into rown + 1, which doesn't exist. - Summing only
dp[n][k]. That counts strings ending in exactlykones, not all valid strings.
Testing
n = 4,k = 2gives13.n = 4,k = 1gives8.n = 3,k = 0gives1.n = 3,k = 3gives8.n = 1,k = 1gives2.n = 5,k = 2gives24.