Start learning free

LESSON 297 OF 414 · BACKTRACKING

Valley Permutations in Rust: Bitmasks Over Values 2 to n

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 12
    Ctrl + Enter to check

    RUST · BACKTRACKING

    Generate Valley Permutations in Rust, explained

    Problem overview

    The function generate_valley_permutations takes n and returns every valley permutation of the numbers 1 to n. A valley permutation first goes down and then goes up, like the shape of a valley: a decreasing run, the smallest value 1 at the bottom, and then an increasing run. Either run may be empty.

    For n = 4 there are eight: 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. The permutation 1 4 2 3 is not a valley: it goes up, down and up again. The function returns each permutation as a String in which every number is followed by a space, such as "2 1 3 4 ".

    Approach: choose a side for each value

    Searching all n! permutations and testing each is wasteful. A valley has so much structure that its shape is determined by a much smaller choice.

    The value 1 is the smallest, so it is the bottom of the valley: the values before it decrease towards it and the values after it increase away from it. Each of the other values, 2 to n, then goes either to the left of 1, in the decreasing part, or to the right, in the increasing part. Once the sides are chosen, the order inside each side is forced: the left values in descending order, the right values in ascending order. So there are exactly 2^(n-1) valley permutations, one per way of assigning n - 1 values to two sides.

    A bitmask encodes such an assignment. The numbers 0 to 2^(n-1) - 1 cover every combination of n - 1 bits. Bit i says which side value i + 2 goes to: set means left, clear means right.

    Step by step

    1. Create result as an empty mutable vector.
    2. Loop over the masks from 0 up to but not including 1 << (n - 1), which is 2^(n-1). The literal is written 1i32 so the shift has the same type as n.
    3. Start two empty vectors, left and right, inside the loop so each mask begins fresh.
    4. Loop over the bits with i from 0 to n - 2. That is one iteration for each of the values 2 to n.
    5. Compute the value that bit stands for: val = i + 2.
    6. Test the bit with (mask >> i) & 1 == 1. If it is set, push val onto left; otherwise onto right.
    7. Order the sides. Sort left in descending order and right in ascending order.
    8. Assemble the permutation: move left into perm, push 1, then push the values of right.
    9. Convert it to text by formatting each number followed by a space and collecting the pieces into one String, then push that into result.
    10. Return result as the final expression.

    The solution in Rust

    impl Solution {
        fn generate_valley_permutations(n: i32) -> Vec<String> {
            let mut result = Vec::new();
            for mask in 0..(1i32 << (n - 1)) {
                let mut left: Vec<i32> = Vec::new();
                let mut right: Vec<i32> = Vec::new();
                for i in 0..(n - 1) {
                    let val = i + 2;
                    if (mask >> i) & 1 == 1 {
                        left.push(val);
                    } else {
                        right.push(val);
                    }
                }
                left.sort_by(|a, b| b.cmp(a));
                right.sort();
                let mut perm = left;
                perm.push(1);
                for v in right {
                    perm.push(v);
                }
                let s: String = perm.iter().map(|x| format!("{} ", x)).collect();
                result.push(s);
            }
            result
        }
    }
    

    The Rust details worth a closer look:

    • Operator precedence. In Rust, & binds tighter than ==, so (mask >> i) & 1 == 1 means ((mask >> i) & 1) == 1. An if condition must be a bool, which is why the explicit comparison is needed: if (mask >> i) & 1 would be a type error.
    • Integer types. mask and i are both i32, like n. The range 0..(n - 1) and the shift mask >> i then need no casts, and the same i is used in i + 2 to get the value.
    • Descending sort. sort() orders ascending. sort_by(|a, b| b.cmp(a)) compares with the arguments swapped, which reverses the order. Both vectors are filled in increasing order of value, so the explicit right.sort() doesn't change anything, but it states the intent.
    • Moving instead of copying. let mut perm = left; moves the vector, and for v in right consumes right, handing each v over by value. Neither vector is used again, so nothing needs cloning.
    • Building the string. perm.iter().map(|x| format!("{} ", x)).collect() produces one small String per number, and collect joins them because the target type is annotated as String. The result always ends with a space.

    Complexity

    Time. There are 2^(n-1) masks. For each one the code makes n - 1 bit tests, sorts two small vectors and builds a string of length proportional to n. The total is about O(2^n * n log n). With n at most 10, that's 512 permutations.

    Space. The output holds 2^(n-1) strings of length proportional to n, so O(n * 2^n). Apart from it, each iteration uses only a few vectors of size n.

    Edge cases and common mistakes

    • n = 1. The only mask is 0, both sides are empty and the answer is ["1 "].
    • n = 2. The answer is ["1 2 ", "2 1 "].
    • The number 1 can't go anywhere else. It always sits where the decreasing part ends and the increasing part begins.
    • Forgetting to sort the left side. Values are pushed in increasing order, so left would be ascending, and a result such as 2 3 1 4 would rise before it falls, which is not a valley.
    • Counting up to the wrong limit. The masks must stop before 1 << (n - 1). Counting to 1 << n would produce each permutation twice, because the extra bit would have no value to control.
    • n = 0. The shift amount n - 1 is then -1, which is not a valid shift. In the optimized builds the lessons use, the shift amount wraps and the loop range ends up empty, so the function returns an empty vector. A debug build would panic with an overflow. The valid inputs start at 1.

    Testing

    • n = 1 gives ["1 "].
    • n = 2 gives ["1 2 ", "2 1 "].
    • n = 3 gives ["1 2 3 ", "2 1 3 ", "3 1 2 ", "3 2 1 "].
    • n = 4 gives eight strings, from "1 2 3 4 " to "4 3 2 1 ".
    • n = 10 gives 512 strings.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free