通过在 A 中选择一个集合然后反转该集合并将该集合插入到 A 的开头,将排列序列 A 转换为 B

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

给定由 N 个数字组成的序列 A 和 B,这些数字是 1,2,3,...,N 的排列。在每一步中,你从左到右依次选择序列A中的一个集合S(被选中的数字将从A中移除),然后反转S并将S中的所有元素添加到序列A的开头。找到一种方法在 log2(n) 步中将 A 转换为 B。


示例:

5         // N
5 4 3 2 1 // A sequence 
2 5 1 3 4 // B sequence 
2 
4 3 1
5 2

我的解决方案是使用回溯法在 log2(n) 步中考虑 S 的所有可能选择。但是,N 太大了,那么有更好的方法吗?谢谢。

对于组合 selecting/removing/prepending 的每个操作,您实际上是在相对于“枢轴”对元素进行排序,并保持顺序。考虑到这一点,您可以按倒序重复“排序”项目(我的意思是,您最后排序最高位),以实现真正的排序。

对于一个明确的例子,让我们举一个例子序列7 3 1 8。用它们在最终排序列表中的各自位置重写术语(将是 1 3 7 8),以获得 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

这个新数组等同于原始数组 - 我们只是使用索引间接引用值(如果你仔细观察的话,我们有点重写未排序列表作为指向已排序列表的指针,而不是值).

现在,让我们用二进制写下这些新值:

2 10
1 01
0 00
3 11

如果我们要对这个列表进行排序,我们将首先按 MSB(最高有效位)排序,然后仅在必要时 对后续位 进行平分,直到我们在 LSB(最低有效位)。同样,我们可以先按 LSB 排序,然后在下一个最高有效位对 all 值排序,并以这种方式继续,直到我们到达 MSB。这将起作用,并正确地对列表进行排序,只要排序是 稳定的 ,也就是说,它不会改变被认为相等的元素的顺序。

让我们通过示例来解决这个问题:如果我们按 LSB 对这些进行排序,我们将得到

2 10
0 00
1 01
3 11

-然后在 MSB 上进行排序(但这次没有打破平局的逻辑),我们会得到:

0 00
1 01
2 10
3 11

-这是正确的排序结果。

还记得开头的“枢轴”分类笔记吗?这就是我们使用这种洞察力的地方。我们将对这个转换后的列表 2 1 0 3 进行排序,从 LSB 到 MSB,没有平局。为此,我们将以标准 <= 0.

为中心

这实际上就是我们在上一个示例中所做的,因此以 space 的名义,我不会再写出来,但请再次查看我们在每个步骤中所做的事情。我们将我们正在检查的位等于 0 的元素移到开头。首先,我们将 2 (10) 和 0 (00) 移动到开头,然后下一次迭代我们将 0 (00) 和 1 (01) 到开头。这正是您的挑战允许您执行的操作。

此外,因为我们的数字被缩减到它们的索引,最大值是 len(array)-1,位数是其中的 log2(),所以总的来说我们只需要做 log2(n) 步骤,正如您的问题陈述所要求的那样。

现在,这在实际代码中是什么样子的?

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)))

如果需要,您当然可以使用按位运算符来完成位魔术 ((thing << bit_idx) & 1),并且您可以 del 列表切片 + 前置而不是 .sort()ing ,这只是一个概念验证,以表明它确实有效。实际输出为:

1 3 7
1 7 9 2
1 2 3 5