Problem overview
The method CountBinaryStrings receives an integer n and returns how many binary strings of length n contain no two adjacent 1s. For n = 3 the answer is 5: 000, 001, 010, 100 and 101. The other three strings of length 3, namely 011, 110 and 111, each contain "11". The notes say n is at most 30.
Approach: look at how a valid string ends
Counting by listing every string would take 2 to the power n steps. Dynamic programming counts them by building on shorter results. Let dp[i] be the number of valid strings of length i, and ask how a valid string of length i can end:
- It ends in 0. The first
i - 1characters can be any valid string, since a 0 can't create a "11". That givesdp[i - 1]strings. - It ends in 1. The character before that 1 must be 0, otherwise "11" appears. So the string ends in "01", and the first
i - 2characters can be any valid string. That givesdp[i - 2]strings.
Those two cases cover all valid strings and don't overlap, so dp[i] = dp[i - 1] + dp[i - 2]. That is the Fibonacci recurrence.
It needs two starting values. The empty string is valid, so dp[0] = 1. Both "0" and "1" are valid, so dp[1] = 2. Then dp[2] = 3 (00, 01, 10), dp[3] = 5, dp[4] = 8 and so on.
Step by step
- Create the table: an
int[]nameddpof lengthn + 1, zero-filled by default. - Set the base cases:
dp[0] = 1anddp[1] = 2. - Loop from 2 while
i <= n, so each entry builds on the two before it and the loop reachesdp[n]. - Add the two cases:
dp[i] = dp[i - 1] + dp[i - 2]. - Return
dp[n].
The solution in C#
class Solution {
public int CountBinaryStrings(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 2;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
C# details worth knowing:
- Two base cases need room.
dp[1]requires an array of at least two elements, sonmust be at least 1. Withn = 0,new int[1]has only index 0 and the assignmentdp[1] = 2throwsIndexOutOfRangeException. The mathematically correct answer forn = 0is 1 (the empty string), so a guard would make the method safe for it. - Size of the answer.
dp[30]is 2,178,309, comfortably inside anint. The values grow by roughly a factor of 1.6 per step, much slower than the 2 per step of all binary strings. - Zero-filled arrays. C# initializes every element of a new
int[]to 0, so entries from 2 up are overwritten before they are read.
Complexity
Time: O(n), one pass with a single addition per step. Space: O(n) for the table. Since each entry uses only the previous two, two variables would be enough for O(1) space.
Edge cases and common mistakes
n = 1. The loop doesn't run and the method returns 2.n = 2. The answer is 3: only "11" is excluded.- Forgetting the "01" detail. A common mistake is to say a string ending in 1 has
dp[i - 1]choices before it. That would allow another 1 right before it. The 1 must be preceded by a 0, which is why the count isdp[i - 2]. - Wrong
dp[0]. If it is 0, thendp[2]comes out as 2 instead of 3. The empty string is a legitimate prefix for the string "01". - Loop bound. Using
i < nleavesdp[n]at 0.
Testing
n = 1gives 2.n = 2gives 3.n = 3gives 5.n = 4gives 8.n = 30gives 2178309.