Problem overview
The method countBinaryStrings(n, k) counts the binary strings of length n that contain exactly k ones, where every one sits on an even position. Positions are numbered from 0, so the even positions are 0, 2, 4 and so on. Zeros can go anywhere.
For n = 6 and k = 2 the even positions are 0, 2 and 4. Choosing two of them for the ones gives 101000, 100010 and 001010, so the answer is 3. The notes promise 2 * k <= n, which means there is always room for the ones.
Approach: build strings one position at a time
You could generate every binary string of length n and test it, but there are 2^n of them, and n can be 30. Generating all of them would be over a billion strings.
Dynamic programming avoids this by counting instead of listing. Let dp[i][j] be the number of strings of length i that have exactly j ones, all on even positions. A string of length i + 1 comes from a string of length i by deciding the character at position i:
- Append a
0. This is always allowed and leaves the number of ones unchanged, sodp[i][j]flows intodp[i + 1][j]. - Append a
1. This is allowed only wheniis even, and it raises the count fromjtoj + 1, sodp[i][j]also flows intodp[i + 1][j + 1]. We skip it oncejhas reachedk, because more thankones is never useful.
Each string is built in exactly one way, so the counts add up without double counting. The answer is dp[n][k].
Here is the table for n = 3 and k = 1. Each row lists the counts for 0 ones and 1 one:
length 0: [1, 0]
length 1: [1, 1] position 0 is even: "0" and "1"
length 2: [1, 1] position 1 is odd: only a 0 can be added
length 3: [1, 2] position 2 is even: "100" and "001" have one 1
The answer, dp[3][1], is 2, matching the strings 100 and 001.
Step by step
- Create the table. Push
n + 1rows, one for each length from 0 ton, and give each rowk + 1zeros, one for each possible number of ones. - Seed the base case. The empty string has zero ones, so
dp[0][0] = 1. Every longer string grows from it. - Loop over positions
ifrom 0 ton - 1. Each pass reads rowiand writes rowi + 1, so the last pass fills rown. - Loop over ones counts
jfrom 0 tok, includingk, so every reachable state passes its ways forward. - Append a zero. Add
dp[i][j]todp[i + 1][j]unconditionally. - Test whether a one fits. The position must be even and
jmust be belowk. - Append a one. Add
dp[i][j]todp[i + 1][j + 1]. The check onjkeepsj + 1inside the row. - Return
dp[n][k]: the strings of full length with exactlykones.
The solution in TypeScript
class Solution {
countBinaryStrings(n: number, k: number): number {
const dp: number[][] = [];
for (let i = 0; i <= n; i++) {
dp.push(new Array(k + 1).fill(0));
}
dp[0][0] = 1;
for (let i = 0; i < n; i++) {
for (let j = 0; j <= k; j++) {
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];
}
}
Some TypeScript details worth knowing:
constfixes the name, not the contents.dpcan never be assigned a different array, butpushand assignments such asdp[0][0] = 1still change it.- Every row must be its own array.
new Array(k + 1).fill(0)runs once per loop pass, so the rows are separate. Writingnew Array(3).fill(new Array(2).fill(0))would put the same inner array in all three slots. After settingshared[0][1] = 5on such a table, all rows print as[ 0, 5 ]. fill(0)matters.new Array(k + 1)alone creates empty slots, and adding to an empty slot givesNaN.- The annotations are only for the compiler.
number[][]checks your code while you write it, but it disappears when TypeScript compiles to JavaScript. - No overflow worry here. A
numberis a double, and the largest answer forn = 30is far below 2^53, so the counts stay exact. ===in the condition.i % 2 === 0compares without converting types, which is the usual TypeScript habit.
Complexity
Time: O(n * k). The two nested loops run n and k + 1 times, and each cell takes a constant amount of work.
Space: O(n * k). The table has (n + 1) * (k + 1) cells. Because row i + 1 only needs row i, two rows would be enough, but the full table makes the idea easier to follow.
Edge cases and common mistakes
k = 0. Only the all-zero string qualifies, so the answer is 1. Forn = 6andk = 0the function returns 1.- More ones than even positions.
n = 3andk = 3returns 0, since only positions 0 and 2 can hold a one. The notes exclude such inputs, but the table handles them. - Counting positions from 1. In
"101000"the ones are at positions 0 and 2. Treating the first character as position 1 would make odd indices the allowed ones. - Stopping the
jloop atk - 1. The zero transition must also run forj = k, or strings that already have all their ones would vanish. - Dropping the
j < ktest. Then the code writes to columnk + 1, which the row doesn't have. JavaScript raises no error: it stretches the row and storesNaNthere (adding toundefinedgivesNaN). The table no longer has the shape you declared, which is a bug waiting to happen.
Testing
n = 6,k = 2gives3.n = 7,k = 2gives6: the even positions are 0, 2, 4 and 6, and there are 6 ways to pick two of the four.n = 5,k = 3gives1: only10101.n = 30,k = 7gives6435.n = 4,k = 0gives1.