python - 一种算法,用于查找 n 个整数的排列,使得对于任何两个数 a[i] 和 a[j] (i < j),它们的平均值不在它们之间
问题描述
给定一个包含 n 个整数的数组,我们如何有效地重新排列其中的数字,使得对于任意两个数字 a[i] 和 a[j] (i < j),它们的平均值不在两个元素之间?
请注意 (i + 2) <= j < n 其中 n 是数组的长度。数字和平均值只允许使用正整数,即对于 1 和 2,平均值为 1.5,我们需要忽略它。原始数组中的数字是不同的。
举个例子 - 如果给定的数组是 arr = [1, 2, 4, 7],这在其当前状态下不是一个有效的排列,因为 arr[0] 和 arr[3] 的平均值是 4,其中 arr [2],所以平均 4 介于 1 和 7 之间。但是 [1, 2, 7, 4] 是一个有效的排列。
我想了想,这是我能想出的解决方案,我找不到算法优化的真正范围。我遇到了一些解决方案,例如基于偶数/奇数索引递归地对数组进行分区并将它们合并回来,但它不适用于某些输入。
from copy import copy
from collections import defaultdict
def arrange_numbers_no_avg_in_between(arr):
def permutations_helper(i):
if i == len(arr) - 1:
num_index_mapping = defaultdict(list)
for idx, num in enumerate(arr):
num_index_mapping[float(num)].append(idx)
for start in range(len(arr) - 2):
for end in range(start + 2, len(arr)):
avg = (float(arr[start]) + float(arr[end])) / 2
if any(start < idx < end for idx in num_index_mapping[avg]):
return
return copy(arr)
else:
for j in range(i, len(arr)):
arr[i], arr[j] = arr[j], arr[i]
result = permutations_helper(i + 1)
if result:
return result
arr[i], arr[j] = arr[j], arr[i]
return permutations_helper(0)
if __name__ == '__main__':
print arrange_numbers_no_avg_in_between([1, 4, 2, 7])
print arrange_numbers_no_avg_in_between([1, 201, 202, 431, 522])
print arrange_numbers_no_avg_in_between(list(range(10)))
这是我收到的输出,
[1, 2, 7, 4]
[1, 201, 202, 431, 522]
[0, 8, 4, 2, 6, 5, 1, 3, 9, 7]
我的算法的时间复杂度似乎是 O(nn!),如果能提供更好、更高效的算法,我将不胜感激。谢谢。
解决方案
简短的回答
Ifarr
是一个非负int
s 的 Python 列表,并且如果数组中没有三次重复的值,则
sorted(arr, key=lambda n: f'{n:b}'[::-1])
是arr
具有所需属性的排列。如果有三次重复的值,那么正如@hilberts_drinking_problem在评论中观察到的,没有排列具有所需的属性。
例子
>>> arr = list(range(10))
>>> arr
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sorted(arr, key=lambda n: f'{n:b}'[::-1])
[0, 8, 4, 2, 6, 1, 9, 5, 3, 7]
更长的答案和解释
如果列表具有所需的属性,我们就称它为“闪亮”。(随意用你最喜欢的形容词替换“闪亮”。命名很难。)
然后有一些光泽的属性很容易检查:
- 闪亮列表的任何子列表(保留顺序)都是闪亮的。例如,
[0, 8, 4, 2, 6, 1, 9, 5, 3, 7]
是闪亮的,所以子列表[0, 4, 2, 9, 3]
是闪亮的。 - 如果是闪亮的,并且我们对 中的每个值
arr
添加或减去一个常量整数,则结果列表仍然是闪亮的。例如,是有光泽的,所以是有光泽的。k
arr
[0, 8, 4, 2]
[1, 9, 5, 3]
- 如果 的所有元素
arr
都是偶数,那么arr
当且仅当我们通过将 的每个元素减半得到的数组是闪亮的时,arr
它才是闪亮的。例如,从[0, 8, 4, 2, 6]
有光泽的事实,我们立即知道它[0, 4, 2, 1, 3]
是有光泽的,反之亦然。
现在让arr
我们的输入列表。然后我们可以通过以下方式找到一个闪亮的排列(如果存在的话):
- 将值
arr
分为偶数值arr_even
和奇数值arr_odd
- 找到偶数值的闪亮排列(稍后会详细介绍)
- 找到奇数值的闪亮排列(同上)
- 将两个排列组合在一起:偶数后跟赔率
现在,如果我们在结果数组中选择任意两个数字:如果它们都是偶数,则它们都属于第一(闪亮)部分,因此它们的平均值不在它们之间。同样,如果他们都是奇怪的。如果一个是奇数,一个是偶数,那么平均值不是整数,所以它不能在列表中。所以结果列表是闪亮的。
但是我们如何找到偶数值的闪亮排列呢?只需将数组的所有元素减半并递归即可。类似地,要找到奇数值的闪亮排列,请计算数组(n-1)//2
中每个的列表n
并递归。
除非列表的所有元素都为零,否则递归会取得进展(列表变短或列表中的值变小)。在这种情况下,如果数组有 2 个或更少的元素,则数组是闪亮的,如果它有 3 个或更多的元素,则没有闪亮的排列。
这是一些代码,为了清晰而不是效率进行了优化:
def shiny(arr):
""" Return a shiny permutation of arr, if one exists. """
# Base case: all zeros
if not any(arr):
if len(arr) > 2:
raise ValueError("No shiny permutation")
return arr
# Recurse: divide into evens and odds
return [
2 * m for m in shiny([n // 2 for n in arr if n % 2 == 0])
] + [
2 * m + 1 for m in shiny([(n - 1) // 2 for n in arr if n % 2 == 1])
]
例子:
>>> shiny([1, 2, 4, 7])
[4, 2, 1, 7]
>>> shiny([1, 201, 202, 431, 522])
[522, 202, 1, 201, 431]
>>> shiny(range(10))
[0, 8, 4, 2, 6, 1, 9, 5, 3, 7]
但我们可以做得更好:查看递归过程,我们首先根据最低有效位进行分离:偶数是最低有效位0
,奇数是最低有效位1
。然后由于我们将所有内容除以二,递归的下一层根据原始数字的第二个最低有效位进行分离,然后在第三个最低有效位上进行下一层,依此类推。在极限情况下,我们所做的只是基于每个整数的二进制扩展进行排序,但最低有效位在前。
所以整个算法可以表示为一种排序,使用反向二进制展开作为键:
>>> sorted(range(10), key=lambda n:f'{n:b}'[::-1])
[0, 8, 4, 2, 6, 1, 9, 5, 3, 7]
推荐阅读
- typescript - 如何在 jest 测试文件中重命名模块
- laravel - 如何在 Livewire 的其他组件中注册组件?
- webpack-dev-server - webpack-dev-server 不接受 'Accept: */*'
- java - 相对于鼠标的缩放在 JavaFX 中不起作用
- perfetto - 如何在 Perfetto traceviewer SQL 上打印 protobuf 结构
- css - 如何拥有流畅的弹性布局
- java - 我应该使用 ++x 而不是 x++ 作为 Java 中的方法参数吗?
- git - Git - 将远程分支与未提交的更改合并并选择他们的
- python - 编码未产生预期结果
- python - 在 Mac 上使用正确版本的 Python 时出现问题