给你一个整数 n,你需要重复执行多次下述操作将其转换为 0 :
翻转 n 的二进制表示中最右侧位(第 0 位)。
如果第 (i-1) 位为 1 且从第 (i-2) 位到第 0 位都为 0,则翻转 n 的二进制表示中的第 i 位。
返回将 n 转换为 0 的最小操作次数。
示例 1:
输入:n = 0
输出:0
示例 2:
输入:n = 3
输出:2
解释:3 的二进制表示为 "11"
"11" -> "01" ,执行的是第 2 种操作,因为第 0 位为 1 。
"01" -> "00" ,执行的是第 1 种操作。
示例 3:
输入:n = 6
输出:4
解释:6 的二进制表示为 "110".
"110" -> "010" ,执行的是第 2 种操作,因为第 1 位为 1 ,第 0 到 0 位为 0 。
"010" -> "011" ,执行的是第 1 种操作。
"011" -> "001" ,执行的是第 2 种操作,因为第 0 位为 1 。
"001" -> "000" ,执行的是第 1 种操作。
示例 4:
输入:n = 9
输出:14
示例 5:
输入:n = 333
输出:393
提示:
0 <= n <= 109
方法一:记忆化搜索(超时)
直接暴力判断当前能够翻转哪一位,并采用记忆化进行优化。
class Solution {
Map<Integer, Integer> map;
public int minimumOneBitOperations(int n) {
map = new HashMap<>();
return dfs(n);
}
private int dfs(int x) {
if (x == 0) return 0;
if (map.containsKey(x)) return map.get(x);
map.put(x, 10000000);
int num = 0, res = Integer.MAX_VALUE;
List<Integer> list = change(x);
int size = list.size();
for (int i = 0; i < size; i++) {
if (list.get(i) == 0)
num++;
else if (num == i && i < size - 1) {
int tmp = x;
tmp ^= 1 << (i + 1);
res = Math.min(res, dfs(tmp) + 1);
}
}
res = Math.min(res, dfs(x ^ 1) + 1);
map.put(x, res);
return res;
}
private List<Integer> change(int x) {
List<Integer> res = new ArrayList<>();
while (x > 0) {
res.add(x % 2);
x /= 2;
}
return res;
}
}
方法二:递归。
本题的目标是找到最少操作次数使得将n变为0,仔细思考下,其实本题存在着一种合法操作顺序使得得到的操作次数一定是最小的,即:每次考虑将最高位的1变为0,。想想为什么?
我们知道,第i位能翻转的条件是:第i-1位是1并且第0位到第i-2位为0,或者i是0。我们发现,无论你之前怎么操作,最高位的1始终是要翻转为0的。而最高位的1能翻转需要满足此时数的二进制表示为110000...000,即我们需要把第i位之前翻转成10000...000,将最高位翻转为0后,剩下的数一定是2的幂次,并且我们验算能够发现2^n变为0的翻转次数是2^(n+1)-1,之后就是不停的递归,直到达到目标。
class Solution {
public int minimumOneBitOperations(int n) {
return f(n);
}
private int f(int n) {
if (n <= 1) return n;
int t = nums(n) - 1;
return (1 << t) + g(n ^ (1 << t), t - 1);
}
private int g(int n, int t) {
if (t == 0) return 1 - n;
if ((n & (1 << t)) != 0)
return f(n ^ (1 << t));
return (1 << t) + g(n, t - 1);
}
private int nums(int n) {
int num = 0;
while (n > 0) {
num++;
n /= 2;
}
return num;
}
}
方法三:格雷码。
格雷码:在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code)。
可以发现:题目中给出的操作,实际上就是从Gray(n)变换为Gray(n-1)的操作。
class Solution {
public int minimumOneBitOperations(int n) {
int ans = 0;
while (n > 0) {
ans ^= n;
n /= 2;
}
return ans;
}
}