Start learning free

LESSON 293 OF 414 · BACKTRACKING

Generate Binary Strings With K Ones in Scala: Backtracking

Practice O(2^n) Read the full explanation

Given two non-negative integers N and K, return the binary strings of length N with exactly K ones.

Example:

Input: N = 4, K = 2
Output: [
    "0011",
    "0101",
    "0110",
    "1001",
    "1010",
    "1100"
]
Notes: K <= N <= 20
STEP 1 OF 11
    Ctrl + Enter to check

    SCALA · BACKTRACKING

    Generate Binary Strings With K Ones in Scala, explained

    Problem overview

    The method generateBinaryStrings takes two integers, n and k, and returns every binary string of length n that contains exactly k ones. The result is an ArrayBuffer[String], and the constraint is k <= n <= 20.

    For n = 4 and k = 2 there are six such strings: 0011, 0101, 0110, 1001, 1010 and 1100. The method returns them in this order, the order you get by comparing the strings as text from smallest to largest. The number of results is always "n choose k", so 6 for this example and 184756 for n = 20, k = 10.

    Approach: build the string one character at a time

    Checking all 2^n strings and counting their ones would work, but it wastes effort on strings that went wrong early. Backtracking builds each string from left to right instead. At every position it has two choices, a '0' or a '1', and it explores both with a recursive call. Each call receives the prefix built so far and the number of ones it is still allowed to place.

    That budget makes the search cheap. Placing a '1' spends one unit of it. When the budget goes below zero, the prefix already has too many ones, and no way of finishing it can fix that, so the whole branch is dropped without looking at the characters that would follow. When the prefix reaches length n, the string is complete, and it is valid only if the budget is exactly zero: any budget left means the string has too few ones.

    Trying '0' before '1' at every position is what makes the results come out in ascending order.

    Step by step

    1. Declare a shared solutions field of type ArrayBuffer[String], a var starting as null. The recursive helper and generateBinaryStrings both need to reach the collected strings, so it lives in the class rather than being passed around.
    2. Reset it at the start of the method with a fresh empty ArrayBuffer[String](). Results from an earlier call can't leak into the next one.
    3. Start the recursion with an empty prefix "", the target length n and the full budget k, then return solutions.
    4. Declare the helper generate with the parameters current, n and k, returning Unit. Here k is the number of ones that may still be placed.
    5. Prune when k is -1. This happens the moment one '1' too many is placed. Because k drops by exactly one per call, it can never skip over -1.
    6. Check whether current has length n. A full-length string must be judged, never extended.
    7. Record it only when k == 0, then return in both cases, so the recursion never grows a string past the target length.
    8. Recurse with '0' appended and k unchanged, since a zero costs nothing.
    9. Recurse with '1' appended and k - 1, since a one spends part of the budget.

    The solution in Scala

    class Solution {
        def generate(current: String, n: Int, k: Int): Unit = {
            if (k == -1) {
                return
            }
            if (current.length == n) {
                if (k == 0) {
                    solutions += current
                }
                return
            }
            generate(current + '0', n, k)
            generate(current + '1', n, k - 1)
        }
    
        var solutions: ArrayBuffer[String] = null
        def generateBinaryStrings(n: Int, k: Int): ArrayBuffer[String] = {
            solutions = ArrayBuffer[String]()
            generate("", n, k)
            return solutions
        }
    }
    

    The Scala details worth a closer look:

    • ArrayBuffer is mutable. It comes from scala.collection.mutable, and += appends an element to it in place. A plain List would be immutable, so every addition would have to produce a new list.
    • current + '0' builds a new string. Scala strings are immutable Java strings, and + with a Char returns a fresh one. current itself doesn't change, so the next line can append '1' to the same prefix. This is why the code needs no explicit "undo" step, even though the technique is called backtracking.
    • length has no parentheses. current.length is how you read a string's size in Scala.
    • return in a Unit method. A bare return leaves the method early. In Scala it is more common to let the last expression be the value, but early returns for base cases read well in a recursive helper.
    • The field is shared state. Because solutions is a var on the object, two threads calling generateBinaryStrings on the same Solution would trample each other. For a single-threaded lesson it is fine, and the reset at the start keeps repeated calls independent.

    Complexity

    Time: every call either stops or adds one character, so the recursion tree has depth at most n. Pruning keeps it from reaching all 2^n leaves. A prefix survives while it has at most k ones, so the work is bounded by the number of such prefixes, times O(n) for building each string. In the worst case this is still exponential, and for small k it is much smaller than 2^n.

    Space: the recursion uses O(n) stack frames, each holding a string of up to n characters, so O(n^2) characters at the deepest point. The output itself is C(n, k) * n characters, which dominates for larger inputs.

    Edge cases and common mistakes

    • k = 0. Only the all-zero string qualifies: n = 3, k = 0 gives ["000"], because the first '1' immediately drives the budget to -1.
    • k = n. Only the all-one string qualifies: n = 3, k = 3 gives ["111"].
    • n = 0. The empty prefix already has length 0, so with k = 0 the result holds a single empty string.
    • Accepting short budgets. Recording a string without testing k == 0 would include strings with too few ones, such as "0001" for k = 2.
    • Forgetting the return after the length check. The recursion would keep appending characters to full-length strings and never finish.
    • Sharing one buffer between calls. If solutions were created once and never reset, a second call would return the first call's strings as well.

    Testing

    • n = 4, k = 2 gives 0011, 0101, 0110, 1001, 1010, 1100.
    • n = 3, k = 0 gives 000.
    • n = 3, k = 3 gives 111.
    • n = 5, k = 2 gives 10 strings.
    • n = 20, k = 10 gives 184756 strings.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free