Problem overview
The method countBinaryStrings receives a non-negative integer n and returns how many binary strings of length n contain no two 1 characters next to each other. For n = 3 there are eight strings in total, and three of them are excluded: 011, 110 and 111. The five that remain are 000, 001, 010, 100 and 101, so the answer is 5. The statement limits n to 30.
Approach: look at how a valid string ends
Checking all 2^n strings is far too slow for n = 30. Instead, count the valid strings of each length from the shorter ones. Look at the end of a valid string of length i:
- If it ends in
0, whatever comes before it can be any valid string of lengthi - 1. A zero can't create a pair of ones. That givesdp[i - 1]strings. - If it ends in
1, the character before must be0, or the string would end in11. So it ends in01, and the part before those two characters is any valid string of lengthi - 2. That givesdp[i - 2]strings.
These two groups don't overlap and cover every valid string, so
dp[i] = dp[i - 1] + dp[i - 2]
This is the Fibonacci recurrence. The base cases are dp[0] = 1, the empty string, and dp[1] = 2, the strings "0" and "1", neither of which can contain two consecutive ones. The table runs 1, 2, 3, 5, 8, and dp[3] = 5 matches the example.
Step by step
- Declare
dpas aconstarray of numbers withn + 1slots, so each index can hold the count for that length. - Store the base cases: 1 string of length 0 and 2 of length 1.
- Loop with
ifrom 2 throughn. Each new count depends on the two before it, so they must be ready first. - Add the previous two entries: a valid string ends either in
0or in01. - Return the last entry,
dp[n], which holds the answer for the full length.
The solution in TypeScript
class Solution {
countBinaryStrings(n: number): number {
const dp: number[] = new Array(n + 1);
dp[0] = 1;
dp[1] = 2;
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
TypeScript details worth knowing:
constwith an array that is filled later.constfixes the binding, not the contents, so assigningdp[0]and the other entries is allowed.new Array(size)has empty slots. Reading one that was never written givesundefined. Every entry the loop reads has been written before, so the code is safe.- Writing past the length grows the array. With
n = 0the array starts with one slot, anddp[1] = 2extends it to two. The answerdp[0]is still the right one, 1. JavaScript arrays resize when you write beyond their end. - Doubles, no overflow. The answer for
n = 30is 2,178,309, nowhere near the limit where integers stop being exact. - Annotations are erased when compiled to JavaScript;
number[]exists for the compiler only.
Complexity
Time: O(n). The loop runs n - 1 times with one addition each.
Space: O(n). The table has n + 1 entries. Each value needs only the previous two, so two variables would be enough for O(1).
Edge cases and common mistakes
n = 0. The answer is 1, the empty string, as set by the base case.n = 1. The answer is 2: both0and1are valid.n = 2. Of00,01,10and11, only11is excluded, so the answer is 3, which isdp[1] + dp[0].- Using
dp[i - 2]for the1ending without the zero. The ending01uses two characters, which is why the rest has lengthi - 2, noti - 1. - Counting
10and01ending separately. A valid string may end in10, but that case is already part of "ends in 0" and counted indp[i - 1]. - Missing base case
dp[0]. Thendp[2]becomes2 + undefined, which isNaN, and every later entry isNaNtoo.
Testing
n = 0gives1.n = 1gives2.n = 2gives3.n = 3gives5.n = 4gives8.n = 30gives2178309.