Start learning free

LESSON 345 OF 414 · FREE CODING INTERVIEW QUESTIONS

Binary Strings with at Most K Consecutive Ones in Scala: DP, O(n*k)

Practice O(n * k) Read the full explanation

Given two non-negative integers N and K, return the number of binary strings of length N with at most K consecutive ones

Example:

Input: N = 4, K = 2
Output: 13
Explanation: [
    "0000",
    "0001",
    "0010",
    "0011",
    "0100",
    "0101",
    "0110",
    "1000",
    "1001",
    "1010",
    "1011",
    "1100",
    "1101",
]
"0111", "1110" and "1111" are not valid because they have more than 2 consecutive ones
Notes: K <= N <= 30
STEP 1 OF 9
    Ctrl + Enter to check

    SCALA · FREE CODING INTERVIEW QUESTIONS

    Binary Strings With At Most K Consecutive Ones in Scala, explained

    Problem overview

    The method countBinaryStrings receives two non-negative integers, n and k, and returns how many binary strings of length n contain no run of more than k ones in a row.

    For n = 4 and k = 2 there are 16 strings of length 4. Three of them break the rule: 0111 and 1110 contain three ones in a row, and 1111 contains four. That leaves 13. The statement guarantees k <= n <= 30.

    Approach: build strings one character at a time

    Checking all 2^n strings works for small n, but with n up to 30 that is over a billion strings. Dynamic programming avoids it by counting strings instead of listing them.

    The key observation is that to decide whether a character can be appended to a valid string, you only need to know how many ones the string currently ends with. Not the whole string. So group the valid strings by length and by that number of trailing ones: dp(i)(j) is the number of valid strings of length i that end in exactly j consecutive ones.

    From a string counted in dp(i)(j) there are two ways to extend it:

    • Append a 0. The run of ones is broken, so the new string, of length i + 1, ends in zero ones. It is always valid.
    • Append a 1. The run grows to j + 1 ones, which is valid only if j < k.

    Every valid string of length i + 1 comes from exactly one valid string of length i plus one character, so pushing the counts forward counts each string once.

    The table for n = 4, k = 2

    Row i lists dp(i)(0), dp(i)(1) and dp(i)(2):

    i = 0:  1 0 0
    i = 1:  1 1 0
    i = 2:  2 1 1
    i = 3:  4 2 1
    i = 4:  7 4 2
    

    Each row's first entry is the sum of the row above, and each other entry is copied from the entry up and to the left. The last row sums to 7 + 4 + 2 = 13, the expected answer.

    Step by step

    1. Create the table as a val named dp with n + 1 rows and k + 1 columns, filled with zeros. Array.ofDim[Int](n + 1, k + 1) does that in one expression. The table itself never gets reassigned, only its cells change, so val is right.
    2. Seed the base case: dp(0)(0) = 1. The empty string is the one valid string of length 0, and it ends in zero ones.
    3. Loop over the lengths with for (i <- 0 until n). The body writes into row i + 1, so i stops at n - 1 and the last row written is n.
    4. Loop over the trailing-ones counts with for (j <- 0 to k). Use to here, which includes k, because a valid string can end in exactly k ones.
    5. Append a 0: dp(i + 1)(0) += dp(i)(j). The += matters, since every j feeds the same cell.
    6. Append a 1, guarded by if (j < k): dp(i + 1)(j + 1) += dp(i)(j). The guard also keeps the index j + 1 inside the row, which has only k + 1 cells.
    7. Declare var result = 0 after the nested loops. Inside a loop it would reset on every pass.
    8. Add up the last row: loop j from 0 to k and do result += dp(n)(j). A valid string of length n may end in anywhere from 0 to k ones.
    9. Return result.

    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 <- 0 until n) {
                for (j <- 0 to k) {
                    dp(i + 1)(0) += dp(i)(j)
                    if (j < k) {
                        dp(i + 1)(j + 1) += dp(i)(j)
                    }
                }
            }
            var result = 0
            for (j <- 0 to k) {
                result += dp(n)(j)
            }
            return result
        }
    }
    

    The Scala details worth a closer look:

    • Array.ofDim[Int](rows, cols). It allocates a two-dimensional array and fills every cell with the default for the type, which is 0 for Int. The type is given in square brackets and the sizes in parentheses.
    • dp(i)(j) += x works on array cells. It expands to reading the cell, adding, and writing back, even though the cell is accessed through method-call syntax.
    • to versus until. 0 to k includes k; 0 until n stops before n. Both appear in this solution, each on purpose.
    • Int is enough. With n <= 30 the largest possible answer is 2^30, which is 1073741824 and fits in a 32-bit Int (maximum 2147483647). For n = 31 or more the count could reach 2147483648 and overflow silently, so a larger n would call for Long.
    • Explicit return. Ending with just result on its own line is the usual style and means the same thing.

    Complexity

    Time: O(n * k). The nested loops run n times k + 1 times, with constant work in each iteration, and the final sum takes O(k).

    Space: O(n * k). The table has (n + 1) * (k + 1) cells. Since each row depends only on the one before it, two rows would be enough, but the lesson keeps the whole table because it is easier to follow.

    Edge cases and common mistakes

    • k = n. Every string is allowed, so the answer is 2^n. For n = 5 and k = 5 the method returns 32.
    • k = 0. No ones are allowed, so only the all-zero string counts and the answer is 1. The table has one column, and the guard j < k is never true.
    • n = 0. The loops over lengths do not run, and dp(0)(0) is the answer: 1, the empty string.
    • k = 1. No two ones may be adjacent. For n = 5 the answer is 13, and for n = 3 it is 5.
    • Using = instead of +=. Each j would overwrite dp(i + 1)(0) instead of adding to it, and only the last j would be counted.
    • Looping j with until k. The states ending in exactly k ones would never be pushed forward or summed, and the answer would come out too small.
    • Dropping the guard. Without if (j < k), appending a 1 to a string with k trailing ones would write to column k + 1 and throw an ArrayIndexOutOfBoundsException.
    • Summing every row. The answer is only the last row. Earlier rows count shorter strings.

    Testing

    • countBinaryStrings(4, 2) gives 13.
    • countBinaryStrings(3, 1) gives 5.
    • countBinaryStrings(5, 1) gives 13.
    • countBinaryStrings(4, 0) gives 1.
    • countBinaryStrings(5, 5) gives 32.
    • countBinaryStrings(30, 30) gives 1073741824.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free