397. 整数替换
给定一个正整数 n ,你可以做如下操作:
如果 n 是偶数,则用 n / 2替换 n 。
如果 n 是奇数,则可以用 n + 1或n - 1替换 n 。
n 变为 1 所需的最小替换次数是多少?
示例 1:
输入:n = 8
输出:3
解释:8 -> 4 -> 2 -> 1
1 <= n <= 2^31 - 1
解法1
看到题目给出 n 有两种状态,分别对应不同操作时,首先想到动态规划。但是再看一眼数据范围,无法创建动态规划数组,会爆内存。
退一步想,dp 递推大部分由递归总结而来,本题用递归做的话,不需要开一个很大的数组,只需要 HashMap 缓存一下结果就行。
确定解法:记忆化递归
class Solution {
HashMap<Long,Integer> map = new HashMap<>();
public int integerReplacement(int n) {
if(n == 1) return 0;
dfs(n);
return map.get((long)n);
}
public int dfs(long n){
if(n == 1) return 0;
if(map.containsKey(n)) return map.get(n);
int res = 0;
if(n % 2 == 0){
res = dfs(n / 2) + 1;
}else{
int v1 = dfs(n - 1);
int v2 = dfs(n + 1);
res = Math.min(v1,v2) + 1;
}
int cnt = map.getOrDefault(n,0x3f3f3f3f);
map.put(n,Math.min(cnt,res));
return res;
}
}
解法2 贪心
贪心的思想自己没想出来,看了大佬的题解才有了一点思路,写篇博客记录一下。–思路不对请大佬纠正。
最终目的是将 n 转换为 1(000…0001)。
当 n 是偶数时,只有一种操作 --> /2。也就是局部最优的。
当 n 是奇数时,分为两种操作,根据操作方式的不同,影响结果。所以当 n 是奇数时,要分情况讨论,选出局部最优的选择。
当 n 是奇数时,如果 +1 / -1 能使后续奇数情况更少,那么对结果的影响更小,答案更优。那么目标就是找到一个"分界点",能够对 +1,-1分情况讨论。
我们可以发现,n 为奇数时,% 4分为两种情况,余数为1,余数为3。
int s = n % 4;
if(s == 1){
n --> n + 1(偶数) --> (n + 1) / 2(奇数) -->...
n --> n - 1(偶数) --> (n - 1) / 2(偶数) -->...
发现当余数为1时,进行 n-1 操作,后续奇数情况更少
n--;
}else{
n --> n + 1(偶数) --> (n + 1) / 2(偶数) -->...
n --> n - 1(偶数) --> (n - 1) / 2(奇数) -->...
发现当余数为1时,进行 n-1 操作,后续奇数情况更少
n++;
}
对总结出的情况进行反证法:
当余数为 1 时,如果进行 +1 操作得到偶数,偶数再 /2 得到结果为奇数:(n + 1) / 2。对 (n + 1) / 2 分情况讨论:
-1 操作得到偶数 (n - 1) / 2。偶数 /2 得到 (n - 1) / 4。共四步。
+1 操作得到偶数 (n + 3) / 2。偶数 /2 得到 (n + 3) / 4。共四步。
当余数为 1 时,进行 -1 操作。得到 (n - 1) / 4。需要 -1 – /2 – /2 三步。得到 (n + 3) / 4 。需要 -1 – /2 – /2 – +1 四步。
总结当余数为 1 时,-1 操作局部更优。
当余数为 3 时,同理,得到 +1 操作局部更优。
但是考虑特例情况,当 n = 3 时。+1 操作 --> 3 – 4 – 2 – 1 四步。 -1 操作 --> 3 – 2 – 1 三步。
总结贪心代码:
class Solution {
//找到特殊点,然后分情况讨论结果。
//偶数只有一种方式,/2,
//只有奇数判断怎么走,才会影响最终结果
//所以越少奇数越好
public int integerReplacement(int n1) {
int res = 0;
long n = n1;
while(n != 1){
if(n % 2 == 0){
// n /= 2;
n >>= 1;
}else{
long s = n % 4;
if(n == 3){
n--;
res++;
continue;
}
if(s == 1){
//n -- n + 1(偶数) -- (n+1)/2 奇数
//n -- n - 1(偶数) -- (n-1)/2 偶数
n--;
}else{
//n -- n + 1(偶数) -- (n+1)/2 偶数
//n -- n - 1(偶数) -- (n-1)/2 奇数
n++;
}
}
res ++;
}
return res;
}
}