Problem overview
The method countBinaryStrings(n, k) returns how many binary strings of length n contain exactly k ones. The notes promise k <= n <= 30.
For n = 4 and k = 2 the strings are 1100, 1010, 1001, 0110, 0101 and 0011, so the answer is 6. Listing the strings would work for tiny inputs, but there are 2^30 strings of length 30, which is more than a billion. The method has to count them without generating them.
Approach: build longer strings from shorter ones
Let dp(i)(j) be the number of binary strings of length i with exactly j ones. Look at the last character of such a string:
- If it is
0, the firsti - 1characters hold alljones. There aredp(i - 1)(j)of those. - If it is
1, the firsti - 1characters holdj - 1ones. There aredp(i - 1)(j - 1)of those.
Every string falls into exactly one of the two groups, so dp(i)(j) = dp(i - 1)(j) + dp(i - 1)(j - 1). This is the rule that builds Pascal's triangle: dp(n)(k) is the binomial coefficient "n choose k", the number of ways to pick which k of the n positions hold a one.
The table needs starting values. The empty string is the only string of length 0 and has no ones, so dp(0)(0) = 1. A string of any length with no ones must be all zeros, so dp(i)(0) = 1.
For k = 2 the table fills in like this. Each row lists the counts for 0, 1 and 2 ones:
length 0: 1 0 0
length 1: 1 1 0
length 2: 1 2 1
length 3: 1 3 3
length 4: 1 4 6
The 6 in the last row is dp(3)(2) + dp(3)(1), which is 3 + 3, and it matches the six strings listed above. The 3 in row 3 is dp(2)(2) + dp(2)(1), which is 1 + 2.
Step by step
- Create the table with
Array.ofDim[Int](n + 1, k + 1). It has a row for each length from 0 tonand a column for each count of ones from 0 tok. Every cell starts at 0. - Set
dp(0)(0)to 1. Every other value is built on this one. - Loop over the lengths with
1 to n. Thetorange includesn, which is what the table needs, since the answer sits in rown. - Set
dp(i)(0)to 1 at the start of each row. The inner loop begins atj = 1and never writes column 0, so this assignment is required. - Loop over the counts of ones with
1 to k. - Apply the rule: add the "last character is 0" count to the "last character is 1" count.
- Return
dp(n)(k).
The order matters. Row i reads only row i - 1, and the outer loop finishes each row before it starts the next, so every value is final when it is read.
The solution in Scala
class Solution {
def countBinaryStrings(n: Int, k: Int): Int = {
val dp = Array.ofDim[Int](n + 1, k + 1)
dp(0)(0) = 1
for (i <- 1 to n) {
dp(i)(0) = 1
for (j <- 1 to k) {
dp(i)(j) = dp(i - 1)(j) + dp(i - 1)(j - 1)
}
}
return dp(n)(k)
}
}
Some Scala details in this code:
Array.ofDim[Int](n + 1, k + 1)allocates all the rows at once, and the type in square brackets is the element type.Intcells start at 0, so there is no initialisation loop. The result is anArray[Array[Int]].- Arrays are indexed with parentheses. Scala writes
dp(i)(j), notdp[i][j], because an array lookup is a method call,apply. The assignmentdp(i)(j) = ...callsupdatein the same way. valfixes the reference, not the contents.dpalways names the same array, but its cells can be changed.1 to nagainst1 until n.toincludes the upper bound anduntilexcludes it. Usinguntilhere would leave rownempty and the method would return 0.for (i <- 1 to n)is a loop over a range. When the range is empty, as in1 to 0, the body never runs.returnworks, but Scala code usually ends with the value as the last expression: writing justdp(n)(k)returns the same result.
Complexity
Time: O(n * k). The table has (n + 1) * (k + 1) cells, and each one takes a constant amount of work.
Space: O(n * k). The table is the only extra memory. Since row i uses only row i - 1, two rows would be enough if memory mattered, but with n <= 30 it doesn't.
Edge cases and common mistakes
- Forgetting
dp(i)(0) = 1. Column 0 would stay at 0 for every row except row 0, and every count in the table would come out too small. Forn = 4,k = 2the answer would be 0 instead of 6. k = 0. The table has a single column and the inner loop never runs. Forn = 5the method returns 1, the all-zeros string.k = n. There is exactly one string, all ones.countBinaryStrings(3, 3)returns 1.n = 0. The outer loop doesn't run anddp(0)(0)is 1, socountBinaryStrings(0, 0)returns 1: the empty string.- Overflow.
Intis 32 bits in Scala and overflow wraps silently. Here the largest value the notes allow iscountBinaryStrings(30, 15), which is 155117520 and fits comfortably. - Off-by-one table size. Rows and columns are counted from 0, so the table needs
n + 1andk + 1of them. Withnandkthe final lookup would be out of bounds.
Testing
n = 4,k = 2gives6.n = 5,k = 0gives1.n = 3,k = 3gives1.n = 0,k = 0gives1.n = 30,k = 15gives155117520.
