Problem overview
The method generateValleyPermutations receives a non-negative integer n and returns every valley permutation of the numbers 1 to n as a number[][]. A valley permutation first decreases and then increases, like a valley between two slopes. The lowest point of the valley is always the value 1.
For n = 4 there are eight of them, in this order: [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, down and up again, so it has a peak in the middle.
Approach: choose a side for every value
The brute-force idea generates all n! permutations and keeps the ones shaped like a valley. For n = 10 that is 3,628,800 permutations to build and test, when only 512 of them qualify.
It is faster to build only valid ones. Since the sequence falls and then rises, the smallest value, 1, must sit at the bottom. Every other value from 2 to n is either on the falling slope to the left of 1 or on the rising slope to the right. Once you decide the side of each value, the permutation is fixed: the left values are written in decreasing order, then 1, then the right values in increasing order.
Each of the n - 1 values from 2 to n has two choices, so there are 2^(n - 1) valley permutations, and each one corresponds to exactly one set of choices.
A bitmask is a convenient way to enumerate all those choices. Count mask from 0 up to 2^(n - 1) - 1. Written in binary, every number in that range is a different pattern of n - 1 bits. Bit i of the mask decides the side of value i + 2: set means left, clear means right.
For n = 4 and mask = 5 (binary 101), bit 0 is set, bit 1 is clear and bit 2 is set. So 2 and 4 go left, 3 goes right. Sorting the left side in decreasing order gives [4, 2], so the permutation is [4, 2, 1, 3].
Step by step
- Create
resultas aconsttypednumber[][]. The annotation is needed because TypeScript can't work out the element type from an empty array literal. - Loop over every mask from 0 up to
(1 << (n - 1)) - 1. Shifting 1 left bykplaces gives2^k, so this visits all2^(n - 1)bit patterns. - Create fresh
leftandrightarrays inside the loop, so that each mask starts from empty slopes.constonly forbids reassigning the variable; pushing into the array is still allowed. - Loop over the bits with
ifrom 0 ton - 2. That isn - 1iterations, one per value from 2 ton. - Compute the value the bit controls:
val = i + 2. The offset is 2 because value 1 needs no bit. - Test the bit.
(mask >> i) & 1moves bitiinto the lowest position and masks out everything else. The result is 0 or 1, so it works directly as anifcondition. Pushvaltoleftif the bit is set, otherwise toright. - Sort the slopes.
leftgoes in descending order, so it falls toward 1, andrightin ascending order, so it rises away from 1. Both need a comparator, as explained below. - Join the pieces with
concat: left, then[1], then right. Push the new array toresult. - Return
resultafter the outer loop has finished.
The solution in TypeScript
class Solution {
generateValleyPermutations(n: number): number[][] {
const result: number[][] = []
for (let mask = 0; mask < (1 << (n - 1)); ++mask) {
const left: number[] = []
const right: number[] = []
for (let i = 0; i < n - 1; ++i) {
const val = i + 2
if ((mask >> i) & 1) {
left.push(val)
} else {
right.push(val)
}
}
left.sort((a, b) => b - a)
right.sort((a, b) => a - b)
const perm = left.concat([1]).concat(right)
result.push(perm)
}
return result
}
}
The TypeScript and JavaScript details worth a closer look:
- Sort needs a comparator. Without one,
sort()converts the elements to strings and compares those.[10, 9, 1].sort()gives[1, 10, 9], which is wrong for numbers.(a, b) => b - asorts descending and(a, b) => a - bascending. Both slopes were filled in increasing order of value, sorightis already ascending, but the explicit sort states the intention. sortworks in place. It changesleftandrightthemselves, which is fine because they are rebuilt for each mask.concatreturns a new array.left.concat([1])doesn't modifyleft, so the two calls can be chained. The result has the typenumber[].- Bit operators work on 32-bit integers.
numberis a double, but<<,>>and&first convert their operands to 32-bit integers. That is plenty here, becausenis at most 10. - Types are erased. The
number[][]annotation helps the compiler and the editor, but nothing checks it when the program runs.
Complexity
Time: O(n * 2^n). There are 2^(n - 1) masks. For each one the inner loop does n - 1 steps, sorting two arrays with a total of n - 1 elements costs at most O(n log n), and concat copies n elements. The sort dominates, so strictly the total is O(n log n * 2^n). The arrays are tiny for n up to 10, so this is fast in practice.
Space: O(n * 2^n). The output alone holds 2^(n - 1) arrays of length n. Apart from the output, the method uses only O(n) memory for left, right and perm.
Edge cases and common mistakes
n = 1. The loop runs formask = 0only, because1 << 0is 1. Both slopes stay empty and the result is[[1]].n = 2. One bit gives two masks and the result[[1, 2], [2, 1]].n = 0. The shift1 << -1uses only the low five bits of the count, so it computes1 << 31, which is the negative number-2147483648. The loop condition is false at once and the method returns[]. That is a reasonable answer for no values, and it is the one the code gives.- Sorting without a comparator. For
nfrom 10 and up, values such as 10 and 9 would be ordered as strings and the slopes would be wrong. - Sharing the arrays between masks. Declaring
leftandrightoutside the outer loop would mix the values of different masks. - Starting the bit values at 1. The value 1 is never assigned to a side. Using
val = i + 1would put 1 on a slope and leavenout. - Order of the output. Counting masks upward gives the order shown in the example, where the position of 2 changes fastest. A different loop would produce the same permutations in another order.
Testing
n = 1gives[[1]].n = 2gives[[1, 2], [2, 1]].n = 3gives[[1, 2, 3], [2, 1, 3], [3, 1, 2], [3, 2, 1]].n = 4gives the eight permutations listed in the overview.n = 10gives 512 permutations.