Problem overview
The method countBinaryStrings receives a non-negative integer n and returns how many strings of length n can be written with the characters 0 and 1. For n = 3 there are eight: 000, 001, 010, 011, 100, 101, 110 and 111, so the answer is 8. The statement limits n to 30.
Only the count is needed, not the strings themselves. That matters, because listing them would take a number of steps proportional to the answer, while counting can be done in a handful of steps.
Approach: each bit doubles the count
Look at how a string of length i is made: take a string of length i - 1 and add one more character, either 0 or 1. Every shorter string can be extended in exactly two ways, and different shorter strings or different final characters never produce the same longer string. So the number of strings of length i is twice the number of length i - 1.
That is a recurrence, dp[i] = 2 * dp[i - 1], with a base case: there are two strings of length 1, "0" and "1". This is dynamic programming in its simplest form: store the answer for each length, and build the next from the previous one. The values go 2, 4, 8, 16, and in closed form the answer is 2 to the power n. The table is kept in the lesson because it shows the recurrence directly, and the same shape returns in the harder problems of this chapter.
Step by step
- Declare the table
dp, anumber[]withn + 1entries so that indexnexists. Entryiwill hold the count for lengthi. - Set the base case:
dp[1]is 2. - Loop with
ifrom 2 up to and includingn, so each length is computed after the one before it. - Double:
dp[i]isdp[i - 1] * 2. - Return
dp[n], the count for the full length.
The solution in TypeScript
class Solution {
countBinaryStrings(n: number): number {
let dp: number[] = new Array(n + 1);
dp[1] = 2;
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] * 2;
}
return dp[n];
}
}
TypeScript details worth knowing:
new Array(n + 1)makes empty slots, not zeros. Reading a slot that was never written givesundefined. This is fine here, because the loop writes every slot before it reads it. When you need zeros, usenew Array(n + 1).fill(0).- The annotation
number[]only helps the compiler. It disappears when the code is compiled to JavaScript, and it doesn't fill the array with anything. - No overflow. A
numberis a double, so there is no integer wrap-around. Forn = 30the answer is 1,073,741,824, far below the point (2 to the power 53) where integers stop being exact. letversusconst.dpis never reassigned, soconstwould be the more usual choice, as in the other lessons of this chapter. The code behaves the same either way.
Complexity
Time: O(n). The loop runs n - 1 times and each pass does one multiplication.
Space: O(n). The table has n + 1 entries. Since each value depends only on the previous one, a single running variable would reduce the space to O(1).
Edge cases and common mistakes
n = 1. The loop doesn't run and the answer isdp[1], which is 2.n = 0. Mathematically there is one string of length 0, the empty string, so the answer should be 1. This solution doesn't handle it: it creates an array with a single slot, thendp[1] = 2grows the array, anddp[0]is never written, so it returnsundefined. Starting the table atdp[0] = 1fixes this. The lesson's tests pass with the code as it is, so this is worth knowing but not required here.- Stopping the loop one early. With
i < nthe last entry is never computed, and the function returnsundefinedforngreater than 1. - Starting the loop at 1. It would compute
dp[1]from the unsetdp[0], andundefined * 2isNaN. - Counting by listing. Building every string works for small
n, but forn = 30there are over a billion of them.
Testing
n = 1gives2.n = 2gives4.n = 3gives8.n = 30gives1073741824.