Search code examples
algorithmsubsetpermutation

Convert the permutation sequence A to B by selecting a set in A then reversing that set and inserting that set at the beginning of A


Given the sequence A and B consisting of N numbers that are permutations of 1,2,3,...,N. At each step, you choose a set S in sequence A in order from left to right (the numbers selected will be removed from A), then reverse S and add all elements in S to the beginning of the sequence A. Find a way to transform A into B in log2(n) steps.

  • Input: N <= 10^4 (number of elements of sequence A, B) and 2 permutations sequence A, B.
  • Output: K (Number of steps to convert A to B). The next K lines are the set of numbers S selected at each step.

Example:

  • Input:
5         // N
5 4 3 2 1 // A sequence 
2 5 1 3 4 // B sequence 
  • Output:
2 
4 3 1
5 2
  • Step 0: S = {}, A = {5, 4, 3, 2, 1}
  • Step 1: S = {4, 3, 1}, A = {5, 2}. Then reverse S => S = {1, 3, 4}. Insert S to beginning of A => A = {1, 3, 4, 5, 2}
  • Step 2: S = {5, 2}, A = {1, 3, 4}. Then reverse S => S = {2, 5}. Insert S to beginning of A => A = {2, 5, 1, 3, 4}

My solution is to use backtracking to consider all possible choices of S in log2(n) steps. However, N is too large so is there a better approach? Thank you.


Solution

  • For each operation of combined selecting/removing/prepending, you're effectively sorting the elements relative to a "pivot", and preserving order. With this in mind, you can repeatedly "sort" the items in backwards order (by that I mean, you sort on the most significant bit last), to achieve a true sort.

    For an explicit example, lets take an example sequence 7 3 1 8. Rewrite the terms with their respective positions in the final sorted list (which would be 1 3 7 8), to get 2 1 0 3.

    7 -> 2  // 7 is at index 2 in the sorted array
    3 -> 1  // 3 is at index 0 in the sorted array
    1 -> 0  // so on
    8 -> 3
    

    This new array is equivalent to the original- we are just using indices to refer to the values indirectly (if you squint hard enough, we're kinda rewriting the unsorted list as pointers to the sorted list, rather than values).

    Now, lets write these new values in binary:

    2 10
    1 01
    0 00
    3 11
    

    If we were to sort this list, we'd first sort by the MSB (most significant bit) and then tiebreak only where necessary on the subsequent bit(s) until we're at the LSB (least significant bit). Equivalently, we can sort by the LSB first, and then sort all values on the next most significant bit, and continuing in this fashion until we're at the MSB. This will work, and correctly sort the list, as long as the sort is stable, that is- it doesn't change the order of elements that are considered equal.

    Let's work this out by example: if we sorted these by the LSB, we'd get

    2 10
    0 00
    1 01
    3 11
    

    -and then following that up with a sort on the MSB (but no tie-breaking logic this time), we'd get:

    0 00
    1 01
    2 10
    3 11
    

    -which is the correct, sorted result.

    Remember the "pivot" sorting note at the beginning? This is where we use that insight. We're going to take this transformed list 2 1 0 3, and sort it bit by bit, from the LSB to the MSB, with no tie-breaking. And to do so, we're going to pivot on the criteria <= 0.

    This is effectively what we just did in our last example, so in the name of space I won't write it out again, but have a look again at what we did in each step. We took the elements with the bits we were checking that were equal to 0, and moved them to the beginning. First, we moved 2 (10) and 0 (00) to the beginning, and then the next iteration we moved 0 (00) and 1 (01) to the beginning. This is exactly what operation your challenge permits you to do.

    Additionally, because our numbers are reduced to their indices, the max value is len(array)-1, and the number of bits is log2() of that, so overall we'll only need to do log2(n) steps, just as your problem statement asks.

    Now, what does this look like in actual code?

    from itertools import product
    from math import log2, ceil
    
    nums = [5, 9, 1, 3, 2, 7]
    size = ceil(log2(len(nums)-1))
    
    bit_table = list(product([0, 1], repeat=size))
    idx_table = {x: i for i, x in enumerate(sorted(nums))}
    
    for bit_idx in range(size)[::-1]:
        subset_vals = [x for x in nums if bit_table[idx_table[x]][bit_idx] == 0]
        nums.sort(key=lambda x: bit_table[idx_table[x]][bit_idx])
    
        print(" ".join(map(str, subset_vals)))
    

    You can of course use bitwise operators to accomplish the bit magic ((thing << bit_idx) & 1) if you want, and you could del slices of the list + prepend instead of .sort()ing, this is just a proof-of-concept to show that it actually works. The actual output being:

    1 3 7
    1 7 9 2
    1 2 3 5