📅  最后修改于: 2023-12-03 14:53:54.719000             🧑  作者: Mango
假设现在有一个整数数组 $nums$,请你找出一个排列,使得对于 $i \in [0, n-1]$,都可以找到一个非负整数 $k$,满足 $2^k = nums[i]$。
现在问:将 $nums$ 转化为符合条件的排列所需的最少步骤是多少?其中一次操作可以将 $nums$ 中的一个数替换为其二进制表示中的 1 的个数。
首先我们需要将 $nums$ 排序,这里选择升序排列。接下来问题就变成了将一个给定的数组变成 $[0, 1, 2, ..., n-1]$ 的最少步骤。
我们考虑从小到大将 $nums$ 中的数一个个与 $[0, 1, 2, ..., n-1]$ 中的数匹配。如果当前数 $nums[i]$ 不能匹配当前位置 $i$,那么需要找到一个可以匹配的数,且在匹配的过程中需要尽可能的不改变已经匹配的数的位置。
假设当前未匹配的数的集合为 $unmatched$,已经匹配的数的集合为 $matched$。我们可以先做一些预处理。观察到 $2^k$ 互不相同,因此不需要考虑 $unmatched$ 中出现相同元素的情况,只需要考虑其中每个数能否匹配。
考虑对于一个数 $num$,找到一个 $2^k$ 与其最接近,即要最小化 $2^k-num$。可以使用二分查找法实现。
def minSteps(nums: List[int]) -> int:
# 排序
nums.sort()
n = len(nums)
# 将 nums 变成 2 的幂的索引,方便查找
pows = [1 << i for i in range(32)]
index = {-1: -1} # 不存在的位置
for i in range(n):
index[nums[i]] = i
steps = 0
unmatched = list(range(n)) # 未匹配的数的索引
matched = set() # 已匹配的数的索引
for i in range(n):
num = 1
while num < nums[i]:
num <<= 1
if num != nums[i]:
j = bisect_left(pows, nums[i]) - 1 # 二分查找
while j >= 0:
if pows[j] not in matched:
break
j -= 1
if j < 0:
return -1
matched.add(pows[j])
unmatched[index[pows[j]]], unmatched[i] = unmatched[i], unmatched[index[pows[j]]]
else:
matched.add(nums[i])
index[nums[i]] = i
steps += bin(nums[i]).count('1')
return steps