📌  相关文章
📜  将给定数组重新排列为2的幂序列所需的最少步骤(1)

📅  最后修改于: 2023-12-03 14:53:54.719000             🧑  作者: Mango

将给定数组重新排列为2的幂序列所需的最少步骤

假设现在有一个整数数组 $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$。可以使用二分查找法实现。

  • 若找到的 $2^k$ 已经在 $matched$ 中出现过,那么继续寻找更小的 $2^k$。
  • 若找到的 $2^k$ 不在 $matched$ 中,那么将其加入 $matched$ 中,并将 $num$ 在 $unmatched$ 中的位置替换为 $2^k$ 的索引。
  • 如果 $unmatched$ 中的所有数都已经匹配,则返回最少步骤。
代码
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
复杂度分析
  • 时间复杂度:$O(n\log n)$,其中 $n$ 为 $nums$ 的长度。排序的时间复杂度为 $O(n \log n)$,对于每个数,都需要做一次二分查找,时间复杂度为 $O(\log n)$,因此总时间复杂度为 $O(n \log n)$。
  • 空间复杂度:$O(n)$,需要使用 $O(n)$ 的空间存储 $index$、$unmatched$ 和 $matched$。