Problem overview
The function countBinaryStrings receives two non-negative Int values, n and k, with k <= n <= 30. It returns how many binary strings of length n contain exactly k ones. Only the count is needed.
For n = 4 and k = 2, the strings are 1100, 1010, 1001, 0110, 0101 and 0011, so the answer is 6.
Approach: look at the last character
Two numbers define a subproblem here, the length and the number of ones, so the table has two dimensions. Let dp[i][j] be the number of binary strings of length i with exactly j ones.
Take such a string and look at its last character.
- If it is
0, the firsti - 1characters hold alljones. There aredp[i - 1][j]such strings. - If it is
1, the firsti - 1characters holdj - 1ones. There aredp[i - 1][j - 1]such strings.
Every string falls into exactly one group, so dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]. This is the rule of Pascal's triangle, and dp[n][k] is the binomial coefficient "n choose k": the number of ways to pick which k of the n positions hold the ones.
The base cases come from the edges. The empty string is the only string of length 0, and it has zero ones, so dp[0][0] = 1. For any length, the single string with no ones is all zeros, so dp[i][0] = 1.
For n = 4 and k = 2, the table fills like this, with one row per length and one column per number of ones:
j=0 j=1 j=2
i=0 1 0 0
i=1 1 1 0
i=2 1 2 1
i=3 1 3 3
i=4 1 4 6
The entry in row 3, column 2 is 3: the strings 011, 101 and 110. It comes from row 2 as dp[2][2] + dp[2][1], which is 1 + 2. The one string in dp[2][2], 11, gets a 0 appended and becomes 110. The two strings in dp[2][1], 01 and 10, get a 1 appended and become 011 and 101. The bottom-right entry, 6, is the answer.
The table also shows a limit. Entries such as dp[1][2] are 0, because a string of length 1 can't hold two ones. The formula produces that on its own: dp[1][2] is dp[0][2] + dp[0][1], and both are 0. So no special handling is needed for counts that are too large for the length.
Step by step
- Create the table.
dpis an array ofn + 1rows, each anIntArrayofk + 1zeros, sodp[i][j]exists for every length and every count of ones up tok. - Set
dp[0][0] = 1, the empty string. Every other entry is built from this one. - Loop
iover1..n, filling one string length at a time. - Set
dp[i][0] = 1for each row. The inner loop starts atj = 1and never touches column 0, so it must be set here. - Loop
jover1..kinside, to compute every count of ones for this length. - Add the two cases:
dp[i - 1][j]for a last character 0, anddp[i - 1][j - 1]for a last character 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 1..n) {
dp[i][0] = 1
for (j in 1..k) {
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]
}
}
return dp[n][k]
}
}
The Kotlin details worth a closer look:
- A 2D table is an array of arrays.
Array(n + 1) { IntArray(k + 1) }calls the lambda once for each of then + 1rows, and each call creates a separate row. Because every row is its own array, changing one entry never affects another row. - Zero by default.
IntArray(k + 1)starts filled with zeros. That is why entries the loops never write, such asdp[0][j]forj > 0, are correctly 0: no string of length 0 has any ones. - Two sets of brackets.
dp[i][j]first picks the rowi, which is anIntArray, and then picks elementjof that row. Unlike a C-style grid, there is nodp[i, j]form for arrays of arrays in Kotlin. - Inclusive ranges.
1..nand1..kinclude their upper ends. Usinguntilwould stop one short. Whenkis 0, the inner range1..0is empty and the function returns the all-zeros count, 1. - Int is enough. The largest answer in the allowed range is for
n = 30andk = 15: 155,117,520, which fits in 32 bits.
Complexity
Time: O(n * k). Each of the n * k inner steps does one addition.
Space: O(n * k). The table has (n + 1) * (k + 1) entries. Row i only reads row i - 1, so two rows would be enough.
Edge cases and common mistakes
k = 0.countBinaryStrings(3, 0)is 1: only 000.k = n.countBinaryStrings(3, 3)is 1: only 111.n = 0. Withk = 0the loops never run and the function returnsdp[0][0], which is 1.- Forgetting
dp[i][0] = 1. Column 0 would stay 0, and then every other entry in the table would be too small. - Wrong table size. The table needs
n + 1rows andk + 1columns. Sizes ofnandkmakedp[n][k]out of range. - Reading
dp[i - 1][j - 1]atj = 0. That index would be -1. The inner loop starts at 1 to avoid it.
Testing
n = 4,k = 2gives 6.n = 5,k = 1gives 5.n = 3,k = 0gives 1.n = 3,k = 3gives 1.n = 30,k = 15gives 155117520.