Start learning free

LESSON 294 OF 414 · BACKTRACKING

Generate Valley Permutations in Scala: Bitmasks and Sorting

Practice O(n!) Read the full explanation

Given a positive integer n, return all the valley permutations of length n.

A valley permutation is defined as a permutation that is a concatenation of two parts: a monotonically decreasing sequence and then a monotonically increasing sequence.

E.g. valley permutations follow this format: [p[0] > p[1] > p[2] > ... > p[k] < p[k+1] < p[k+2] < ... < p[n-1]].


Example:

Input: n = 4

Output:[
    [1, 2, 3, 4],
    [2, 1, 3, 4],
    [3, 1, 2, 4],
    [3, 2, 1, 4],
    [4, 1, 2, 3],
    [4, 2, 1, 3],
    [4, 3, 1, 2],
    [4, 3, 2, 1]
]

Explanation:
Permutations such as [1, 4, 2, 3] are not valleys because 1 < 4 > 2 < 3

Notes: n <= 10


STEP 1 OF 16
    Ctrl + Enter to check

    SCALA · BACKTRACKING

    Generate Valley Permutations in Scala, explained

    Problem overview

    The method generateValleyPermutations takes an integer n and returns every permutation of the numbers 1 to n that forms a valley: a strictly decreasing run followed by a strictly increasing run. The result is a mutable.ArrayBuffer[Array[Int]], with n <= 10.

    For n = 4 the answer has eight permutations: [1, 2, 3, 4], [2, 1, 3, 4], [3, 1, 2, 4], [3, 2, 1, 4], [4, 1, 2, 3], [4, 2, 1, 3], [4, 3, 1, 2] and [4, 3, 2, 1]. A permutation such as [1, 4, 2, 3] is not a valley: it goes up, then down, then up again.

    Approach: choose a side for every value

    A valley has one lowest point, and the lowest of the numbers 1 to n is 1. So 1 always sits at the bottom, with a decreasing run on its left and an increasing run on its right (either run may be empty).

    Once you know which values are on the left and which are on the right, the permutation is fixed. The left values must appear largest first, the right values smallest first. So each valley permutation is determined by one decision per value from 2 to n: left or right. That is n - 1 independent two-way choices, hence 2^(n - 1) valley permutations, and for n = 10 that is 512.

    A bitmask represents such a set of choices neatly. Count a number mask from 0 up to 2^(n - 1) - 1. Every such number is a different combination of n - 1 bits, and bit i says which side the value i + 2 goes on. This avoids generating all n! permutations and filtering them, which for n = 10 would mean 3,628,800 candidates instead of 512.

    Step by step

    1. Create result, an empty mutable.ArrayBuffer[Array[Int]].
    2. Loop over the masks from 0 up to (1 << (n - 1)) - 1. Shifting 1 left by n - 1 places gives 2^(n - 1), and until leaves that upper bound out.
    3. Create fresh left and right buffers inside the loop, so nothing from the previous mask leaks in.
    4. Loop i over the bits, 0 until n - 1. That is one iteration per value from 2 to n.
    5. Compute value = i + 2. The offset is 2 because 1 sits at the bottom and needs no bit.
    6. Test bit i with ((mask >> i) & 1) == 1. A set bit sends the value to left, a clear bit to right.
    7. Sort the halves: left in descending order and right in ascending order, so they form the two sides of the valley.
    8. Assemble the permutation in an Array[Int] of size n: copy left, store 1, then copy right, advancing an index after each write. Append the array to result.
    9. Return result after the outer loop has finished.

    The solution in Scala

    class Solution {
        def generateValleyPermutations(n: Int): mutable.ArrayBuffer[Array[Int]] = {
            val result = mutable.ArrayBuffer[Array[Int]]()
            for (mask <- 0 until (1 << (n - 1))) {
                val left = mutable.ArrayBuffer[Int]()
                val right = mutable.ArrayBuffer[Int]()
                for (i <- 0 until n - 1) {
                    val value = i + 2
                    if (((mask >> i) & 1) == 1) {
                        left += value
                    } else {
                        right += value
                    }
                }
                left.sortInPlaceWith(_ > _)
                right.sortInPlaceWith(_ < _)
                val perm = new Array[Int](n)
                var idx = 0
                for (v <- left) {
                    perm(idx) = v
                    idx += 1
                }
                perm(idx) = 1
                idx += 1
                for (v <- right) {
                    perm(idx) = v
                    idx += 1
                }
                result += perm
            }
            return result
        }
    }
    

    The Scala details worth a closer look:

    • Bit operators. << and >> shift an Int, and & keeps only the bits set in both operands. (mask >> i) & 1 moves bit i to the lowest position and masks everything else away, leaving 0 or 1.
    • The comparison is explicit and the parentheses matter. An if condition must be a Boolean, so the 0 or 1 is compared with == 1. In Scala, == binds more tightly than &, so (mask >> i) & 1 == 1 would be read as (mask >> i) & (1 == 1) and the compiler rejects it, because & on an Int can't take a Boolean.
    • until excludes its end. 0 until n - 1 gives 0, 1, ..., n - 2. The arithmetic n - 1 is evaluated before until, because until is an ordinary method with lower precedence than -.
    • sortInPlaceWith sorts the buffer itself and takes a "comes before" function: _ > _ puts larger values first, _ < _ smaller values first. The values were added in increasing order, so right is already sorted; the second sort just makes the intent explicit.
    • No ++ in Scala. That is why each increment is its own idx += 1 statement.
    • Arrays inside the buffer. Each permutation is a fresh Array[Int], so appending it doesn't alias an earlier one. Printing an Array directly shows something like [I@1a2b3c; use mkString to print its contents.

    Complexity

    Let m = 2^(n - 1) be the number of permutations.

    Time: O(2^n * n log n). Each of the m masks costs O(n) to split the values, up to O(n log n) to sort the two halves, and O(n) to assemble the array. Because the output has m * n numbers, any solution needs at least O(2^n * n).

    Space: O(2^n * n) for the result itself, plus O(n) for the temporary buffers.

    Edge cases and common mistakes

    • n = 1. The loop runs for masks 0 until 1 << 0, which is just mask 0. Both halves stay empty and the result is [1].
    • n = 2. The result is [2, 1] and [1, 2], one of each side for the value 2.
    • n = 3. The result is [1, 2, 3], [2, 1, 3], [3, 1, 2] and [3, 2, 1].
    • Counting the bit for 1. Using n bits instead of n - 1 would double the output with duplicates. Value 1 never needs a side.
    • Wrong sort direction. If left were ascending, the permutation would rise and fall instead of forming a valley.
    • Reusing the buffers across masks. Declaring left and right outside the loop would carry values over from one mask to the next, and perm would overflow its size.

    Testing

    • n = 1 gives [1].
    • n = 2 gives [1, 2] and [2, 1], in that order.
    • n = 3 gives [1, 2, 3], [2, 1, 3], [3, 1, 2], [3, 2, 1].
    • n = 4 gives the eight permutations listed in the overview, in that order.
    • n = 10 gives 512 permutations.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free