📅  最后修改于: 2023-12-03 14:49:35.828000             🧑  作者: Mango
在一些比特位操作的场景中,我们需要将一个整数的某些位翻转,使其变成目标形态。其中一种形态是,将二进制数的右侧全为 1,左侧全为 0,也就是形如 000...0111...1 的形态。
在这种情况下,我们需要计算最少需要翻转多少位,才能达到目标形态。在本文中,我们将介绍两种不同的算法,并用 Python 和 C++ 两种语言进行实现。
最朴素的方法是直接枚举每一位,计算翻转每一位所需要的翻转次数,然后取最小值即可。由于题目要求将左侧全设为 0,因此我们只需要尝试翻转右侧 1 的数量,直到达到目标形态。
下面是 Python 的实现:
def min_flips(num):
target = 0
for i in range(num.bit_length()):
target |= 1 << i
return sum((num >> i) & 1 != ((target >> i) & 1) for i in range(num.bit_length() - 1, -1, -1))
print(min_flips(0))
print(min_flips(1))
print(min_flips(100))
这里我们首先计算目标形态,也就是将右侧所有比特都置为 1。然后,我们从最高位开始向低位遍历,将第 $i$ 位翻转所需要的次数就是将 $(num >> i)$ 与 $0$ 或 $1$ 进行异或的结果。最后将所有翻转次数加起来就是答案了。
虽然这个算法非常简单,但是由于要遍历每一位,时间复杂度为 $O(\log n)$,其中 $n$ 是整数的位数。在 LeetCode OJ 上,这个算法能够通过本题。
如果我们仔细观察一下目标形态 $000...0111...1$,不难发现这个比特串可以表示为:
$$ \text{target} = (\text{1 << num_bits}) - 1 $$
其中 $\text{num_bits}$ 是整数的二进制表示中的位数。因此,我们可以将目标形态 $\text{target}$ 和输入的整数 $num$ 做异或运算,得到一个新的比特串 $\text{diff}$,也就是原先的某些位被翻转了。比如:
$$ \text{num} = \texttt{0001011001},\quad \text{target} = \texttt{0001111111},\quad \text{diff} = \texttt{0000100110} $$
接下来,我们需要计算将所有的 1 都移到右侧所需要的翻转次数。这个时候,我们可以使用一个变化量 $k$,代表当前比特串中右侧已经连续的 1 的数量。假设当前位是 $x$,那么翻转这一位所造成的影响是:
接下来,我们对所有的比特位按照从低位到高位的顺序依次进行处理,即可得到最少的翻转次数。在这个过程中,由于我们只需要知道 $k$ 的奇偶性,因此可以使用位运算进行计算。
下面是 C++ 的实现:
class Solution {
public:
int minFlips(int num) {
int target = (1 << num_bits(num)) - 1, diff = num ^ target, k = 0, ans = 0;
for (int i = 0; i < num_bits(num); i++) {
int x = (diff >> i) & 1;
if (x == 1) {
ans += (k % 2 == 0) ? 1 : 0;
k++;
} else {
ans += (k % 2 == 1) ? 1 : 0;
}
}
return ans;
}
private:
int num_bits(int x) {
return 32 - __builtin_clz(x);
}
};
这里我们使用 C++ 内置函数 __builtin_clz
,该函数可以在 $O(1)$ 的时间复杂度内计算一个整数的二进制表示中前导 0 的数量。由于 $32 - \text{clz}(x)$ 就是 $x$ 二进制表示中的位数,因此我们不需要额外计算它。
这个算法的时间复杂度仅为 $O(\log n)$,性能非常高。在 LeetCode OJ 上,这个算法的执行效率远远优于暴力枚举。