Problem overview
The method CountBinaryStrings takes two non-negative integers, n and k, with k <= n <= 30. It returns how many binary strings of length n never contain more than k ones in a row.
For n = 4 and k = 2 there are 16 strings of length 4, and three of them break the rule: 0111, 1110 and 1111 each contain three ones in a row. The answer is 16 - 3 = 13.
Approach: build strings one character at a time
Listing all 2^n strings and checking each one is the brute force. With n up to 30 that is over a billion strings, which is too slow. Dynamic programming counts the strings without listing them.
To decide whether a new character is allowed, you only need to know how many ones the string currently ends with. So define dp[i, j] as the number of valid strings of length i that end in exactly j ones. Every valid string has j between 0 and k.
Take a string counted in dp[i, j] and append one character:
- Append
0. The run of ones is broken, so the new string of lengthi + 1ends in 0 ones. This is always allowed. - Append
1. The run grows toj + 1. This is allowed only whenj < k.
Every valid string of length i + 1 comes from exactly one valid string of length i by one of these two moves, so no string is missed or counted twice. The answer is the sum of row n.
For n = 4 and k = 2 the table, with columns j = 0, 1, 2, is:
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
You can check one cell by hand. dp[3, 1] is 2: the length-3 strings ending in exactly one 1 are 001 and 101. It comes from appending a 1 to the two strings in dp[2, 0], which are 00 and 10. The last row sums to 7 + 4 + 2 = 13, which matches the example.
Step by step
- Create the table as a rectangular
int[,]withn + 1rows (lengths 0 ton) andk + 1columns (0 toktrailing ones). C# fills a new numeric array with zeros. - Seed it:
dp[0, 0] = 1. The empty string is the one string of length 0, and it ends in zero ones. Every other string grows from it. - Loop over lengths
ifrom 0 ton - 1. The body writes into rowi + 1, so the last row,n, is only written, never used as a source. - Loop over
jfrom 0 tok, inclusive, since strings can end in up tokones. - Append a 0: add
dp[i, j]intodp[i + 1, 0]. Use+=, because every value ofjfeeds that same cell. - Append a 1 only if
j < k: adddp[i, j]intodp[i + 1, j + 1]. Without the guard the indexk + 1would fall outside the table, and the strings would break the limit anyway. - Declare
resultas 0 after the loops, then add eachdp[n, j]forjfrom 0 tokinto it. - Return
result.
The solution in C#
class Solution {
public int CountBinaryStrings(int n, int k) {
int[,] dp = new int[n + 1, k + 1];
dp[0, 0] = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= k; j++) {
dp[i + 1, 0] += dp[i, j];
if (j < k) {
dp[i + 1, j + 1] += dp[i, j];
}
}
}
int result = 0;
for (int j = 0; j <= k; j++) {
result += dp[n, j];
}
return result;
}
}
The C# details worth knowing:
- Rectangular array.
new int[n + 1, k + 1]creates a table whose cells are read and written asdp[i, j]. Every cell starts at 0, so+=works from the first use with no setup. +=on an array cell.dp[i + 1, 0] += dp[i, j]is the same as writing the cell on both sides of an assignment. The index expression is evaluated once.- Sizes in the declaration, not the type. The type is
int[,], and the lengths appear only in thenewexpression. Out-of-range indexes throw anIndexOutOfRangeExceptioninstead of reading garbage. - Overflow is not a concern here. Each count is at most
2^n, and2^30is 1,073,741,824, belowint.MaxValue(2,147,483,647). Forn = 31the total could pass theintrange; C# would then wrap around silently, so alongwould be needed.
Complexity
Time: O(n * k). The nested loops run n times k + 1 iterations, with constant work each. The final sum adds O(k).
Space: O(n * k). The table has (n + 1) * (k + 1) cells. Each row depends only on the previous one, so two rows would be enough if memory mattered.
Edge cases and common mistakes
k = 0. No ones are allowed, so only the all-zero string is valid, and the answer is 1. The table has one column and thej < ktest is never true.k = n. Every string is valid, so the answer is2^n. Forn = 3, k = 3it returns 8.n = 0. The loops don't run anddp[0, 0]is 1, so the empty string is counted once.- Small checks.
n = 3, k = 1returns 5:000,001,010,100,101. - Looping
iup ton.dp[i + 1, ...]would then write to rown + 1, which does not exist. - Using
=instead of+=. Each pass overjwould overwritedp[i + 1, 0], keeping only the last term. - Forgetting the
j < kguard. It either crashes with an out-of-range index or counts invalid strings. - Summing the whole table. Only the last row counts strings of length
n.
Testing
n = 4, k = 2gives13.n = 3, k = 1gives5.n = 3, k = 3gives8.n = 5, k = 0gives1.n = 1, k = 1gives2.