1. 问题背景
给定一个乱序数组:
[7, 8, 1, 5, 3, 4, 2, 0, 9, 6]
将其从小到大排序后可以得到:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
从乱序到有序只需要调用一下 sort
函数,但要从有序恢复至原先的乱序又该如何做呢?
2. 解决方案
我们可以在排序的时候记录下索引的变化。起初:
Array: [7, 8, 1, 5, 3, 4, 2, 0, 9, 6]
Index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
排序后:
Array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Index: [7, 2, 6, 4, 5, 3, 9, 0, 1, 8]
根据 Index
的变化可以建立如下的映射:
7 → 0 2 → 1 6 → 2 4 → 3 5 → 4 3 → 5 9 → 6 0 → 7 1 → 8 8 → 9 7\to 0 \\ 2\to 1 \\ 6\to 2 \\ 4\to 3 \\ 5\to 4 \\ 3\to 5 \\ 9\to 6 \\ 0\to 7 \\ 1\to 8 \\ 8\to 9 \\ 7→02→16→24→35→43→59→60→71→88→9
排序的过程可以认为是把箭头左边对应位置上的数字移动到箭头右边对应的位置上,例如 6 → 2 6\to2 6→2 代表把第6个位置上的数字移动到第2个位置上。若要恢复原先的乱序,就要把第2个位置上的数字移动到第6个位置上。假设恢复前的数组为 _res
,恢复后的数组为 res
,那么不难看出有 res[6] = _res[2]
。
如何获得上面的映射呢?首先我们可以获得排序后的 Index
数组,然后根据这个数组建立一个逆映射:
import random
random.seed(0)
a = list(range(10))
random.shuffle(a)
print("Array:", a)
# Array: [7, 8, 1, 5, 3, 4, 2, 0, 9, 6]
b = sorted(enumerate(a), key=lambda x: x[1])
idx_map, sorted_a = zip(*b)
print(idx_map)
# (7, 2, 6, 4, 5, 3, 9, 0, 1, 8)
inverse_idx_map = {j: i for i, j in enumerate(idx_map)}
恢复原先的乱序数组只需要新开一个数组,然后遍历这个逆映射即可:
c = [None] * 10
for k, v in inverse_idx_map.items():
c[k] = sorted_a[v]
print(c)
# [7, 8, 1, 5, 3, 4, 2, 0, 9, 6]
有一个问题是,空间复杂度一定是 O ( n ) O(n) O(n) 吗?可不可以降到 O ( 1 ) O(1) O(1)?答案是不可以。假设在某一步执行了 _res[k] = _res[v]
,那么位于 k
处的元素就会被彻底覆盖掉,无法再恢复。
据此可以总结整个算法流程:
- 首先算出排序后的索引到排序前的索引映射
inverse_idx_map
。 - 遍历这个映射,执行
res[k] = _res[v]
,其中_res
是恢复前的数组,res
是恢复后的数组。
模版:
def get_inverse_idx_map(arr, sort_func):
idx_map, sorted_arr = zip(*sorted(enumerate(arr), key=sort_func))
inverse_idx_map = {j: i for i, j in enumerate(idx_map)}
return inverse_idx_map, sorted_arr
def restore_arr(inverse_idx_map, sorted_arr):
restored_arr = [None] * len(sorted_arr)
for k, v in inverse_idx_map.items():
restored_arr[k] = sorted_arr[v]
return restored_arr