Problem overview
The function countBinaryStrings(n, k) returns how many binary strings of length n contain exactly k ones, with every one standing on an even position. Positions are counted from 0, so the even positions are 0, 2, 4 and so on. Every odd position must hold a 0.
For n = 6 and k = 2 the answer is 3. The even positions are 0, 2 and 4, and the three ways to choose two of them give 101000, 100010 and 001010. The notes guarantee 2 * K <= N <= 30.
Approach: build the string one character at a time
Listing every binary string of length 30 is out of the question, since there are over a billion of them. Dynamic programming avoids that by counting instead of listing. Define dp[i][j] as the number of valid strings of length i that contain j ones. Every longer string is a shorter valid string plus one more character, so each row of the table comes from the row before it.
The new character goes at position i, because a string of length i fills positions 0 to i - 1. There are two choices:
- A 0 is always allowed. The count of ones stays at
j, sodp[i][j]flows intodp[i + 1][j]. - A 1 is allowed only when
iis even and we still need more ones. The count becomesj + 1, sodp[i][j]flows intodp[i + 1][j + 1].
The starting point is the empty string: one string of length 0 with zero ones. The answer is the cell for full length and exactly k ones, dp[n][k].
There is also a shortcut worth noticing. The odd positions are fixed at 0, so the only real decision is which k of the even positions get a 1. A string of length n has (n + 1) / 2 even positions (integer division), so the answer is the binomial coefficient "choose k of those". For n = 6 and k = 2 that is 3 choose 2, which is 3. The table computes the same numbers without needing a formula, and the same technique carries over to rules a formula can't capture.
Step by step
- Create the table. It has
n + 1rows because lengths run from 0 ton, andk + 1columns because the number of ones runs from 0 tok. - Seed the empty string. Set
dp[0][0]to 1. Everything else starts at 0 and is filled from this one cell. - Loop over positions
ifrom 0 ton - 1. Each pass decides the character at positioniand fills rowi + 1. - Loop over the counts
jfrom 0 tokinclusive. Every reachable state has to pass its ways forward, andkitself is a valid column. - Place a 0. Add
dp[i][j]todp[i + 1][j]with no condition. - Guard the 1. Check that
iis even andj < k. The second test also keepsj + 1inside the table. - Place a 1. Add
dp[i][j]todp[i + 1][j + 1]. - Return
dp[n][k].
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][j] += dp[i][j]
if (i % 2 == 0 && j < k) {
dp[i + 1][j + 1] += dp[i][j]
}
}
}
return dp[n][k]
}
}
The Kotlin details worth a closer look:
- Building a 2D table.
Array(n + 1) { IntArray(k + 1) }calls the lambda once per row, so every row is its ownIntArray. AnIntArraystarts filled with zeros, which is what the table needs. valfixes the name, not the contents.dpalways refers to the same array of rows, but the cells can still change, sodp[0][0] = 1compiles.- Ranges.
0 until nstops atn - 1, while0..kincludesk. Mixing them up is the classic off-by-one error: with0 until kthe columnkwould never pass its count forward. +=on an array element.dp[i + 1][j] += dp[i][j]reads the cell, adds, and stores the result back. Several earlier states can feed one cell, so the update has to add rather than assign.Intis enough. WithN <= 30there are at most 15 even positions, so the largest answer is 6435, the number of ways to pick 7 of 15 positions.
Complexity
Time: O(n * k). The two loops visit each of the n positions and each of the k + 1 counts once, and every visit does a constant amount of work.
Space: O(n * k). The table has (n + 1) * (k + 1) cells. Since row i + 1 only reads row i, two rows would suffice, but the full table is easier to follow.
Edge cases and common mistakes
k = 0. Only the all-zero string qualifies, socountBinaryStrings(4, 0)returns 1.- Every even position holds a one.
countBinaryStrings(5, 3)returns 1: positions 0, 2 and 4 are the only candidates, and all three are used. - Too many ones.
countBinaryStrings(3, 2)returns 1, since positions 0 and 2 are both even. Asking for more ones than there are even positions gives 0, because no path reaches that cell. - Counting positions from 1. In the example,
101000has its ones at positions 0 and 2. If you treat position 1 as the first even position, the table counts a different set of strings. - Dropping the
j < ktest. Atj = kthe write todp[i + 1][j + 1]goes past the last column and throwsArrayIndexOutOfBoundsException. - Using
i + 1in the parity test. The character being placed sits at positioni, soi % 2 == 0is the right test.
Testing
n = 6,k = 2gives3.n = 4,k = 0gives1.n = 5,k = 3gives1.n = 30,k = 5gives3003.n = 30,k = 15gives1.