题目地址:
https://leetcode.com/problems/minimum-one-bit-operations-to-make-integers-zero/
给定一个正整数
n
n
n,将其视为二进制数。允许进行两种操作:
1、将其最低位取反;
2、如果左起第
i
i
i位是
1
1
1,并且第
i
−
1
i-1
i−1位一直到第
0
0
0位都是
0
0
0,那就可以将第
i
+
1
i+1
i+1位取反。
问要将其变为
0
0
0至少需要多少步。
首先,对于任意 n n n,它一定要先变成形如 1100...0 1100...0 1100...0的数,才能继续变为 0 0 0。这是因为要将 n n n变为 0 0 0,必须其中某一步是将最高位变为 0 0 0,而最高位变为 0 0 0必须要得到形如 1100...0 1100...0 1100...0的数才能做。我们来证明两个命题:
1、对于
n
=
2
k
n=2^k
n=2k来说,至少需要
2
k
+
1
−
1
2^{k+1}-1
2k+1−1步。
数学归纳法:当
k
=
0
k=0
k=0时显然。假设对于
k
−
1
k-1
k−1的情况上述命题也正确,对于
k
k
k的情况,由于
10...0
10...0
10...0先要变成
110...0
110...0
110...0,对于任意的能把
10...0
10...0
10...0变成
110...0
110...0
110...0的最短的操作序列,都不会改动最高位的
1
1
1(如果改变了的话,说明中途已经到了
110...0
110...0
110...0了,与最短矛盾),所以把
10...0
10...0
10...0变成
110...0
110...0
110...0的最短的操作序列其实就是把
0
0
0变为
10...0
10...0
10...0的操作序列(即只考虑除了最高位
1
1
1的剩余数字)。而题目的两个操作实际上是在做异或运算,这个运算是可逆的,所以把
0
0
0变为
10...0
10...0
10...0的最短操作序列的逆序就是把
10...0
10...0
10...0变为
0
0
0的最短操作序列,从而根据归纳假设,其有
2
k
−
1
2^k-1
2k−1步,接着
110...0
110...0
110...0变成
10...0
10...0
10...0需要
1
1
1步,然后再根据归纳假设,其经过
2
k
−
1
2^k-1
2k−1步变为
0
0
0,所以总共步数就是
2
k
−
1
+
2
k
=
2
k
+
1
−
1
2^k-1+2^k=2^{k+1}-1
2k−1+2k=2k+1−1步。由数学归纳法,命题对于任意
k
k
k都对。
2、将
n
→
x
n\to x
n→x的最少步数等于
n
∧
x
→
x
∧
x
=
0
n\wedge x\to x\wedge x=0
n∧x→x∧x=0的最少步数,这一点由异或运算满足结合律显然成立(左边同时做异或即可)。
我们回到原问题,设 f ( n ) f(n) f(n)是答案。对于 n n n而言, n → 110...0 n\to 110...0 n→110...0需要的步数就等于 n ∧ 110...0 → 110...0 ∧ 110...0 = 0 n\wedge 110...0\to 110...0\wedge 110...0=0 n∧110...0→110...0∧110...0=0的步数,我们取 110...0 110...0 110...0是能将 n n n的最高位 1 1 1通过异或消掉的数,令 k k k是小于等于 n n n的最大的 2 2 2的幂(例如 n = 10 n=10 n=10的时候 k = 2 3 = 8 k=2^3=8 k=23=8, n = 17 n=17 n=17的时候 k = 2 4 = 16 k=2^4=16 k=24=16),那么显然 k + k / 2 k+k/2 k+k/2就是形如 110...0 110...0 110...0并且能将 n n n的最高位 1 1 1消掉的数。所以有 f ( n ) = f ( n ∧ 110...0 ) + 1 + k − 1 = f ( n ∧ 110...0 ) + k f(n)=f(n\wedge 110...0)+1+k-1=f(n\wedge 110...0)+k f(n)=f(n∧110...0)+1+k−1=f(n∧110...0)+k只需要直接DFS就行了。因为每次参数的最高位都会被消掉,所以位数一定会越变越少,时间复杂度就是 O ( log n ) O(\log n) O(logn)。代码如下:
public class Solution {
public int minimumOneBitOperations(int n) {
return dfs(n);
}
private int dfs(int n) {
if (n == 0) {
return 0;
}
int k = 1;
while (k << 1 <= n) {
k <<= 1;
}
return dfs(k ^ (k >> 1) ^ n) + k;
}
}
时空复杂度 O ( log n ) O(\log n) O(logn)。