给定一个正整数 n ,你可以做如下操作:
如果 n 是偶数,则用 n / 2替换 n 。
如果 n 是奇数,则可以用 n + 1或n - 1替换 n 。
n 变为 1 所需的最小替换次数是多少?
示例 1:
输入:n = 8
输出:3
解释:8 -> 4 -> 2 -> 1
示例 2:
输入:n = 7
输出:4
解释:7 -> 8 -> 4 -> 2 -> 1
或 7 -> 6 -> 3 -> 2 -> 1
示例 3:
输入:n = 4
输出:2
提示:
1 <= n <= 2^31 - 1
思路一:直接根据题意走,integerReplacement(n)表示将n化为1需要的最少步骤
当n为奇数,则
integerReplacement(n) = min(integerReplacement(n + 1), integerReplacement(n - 1)) + 1
当n为偶数,则
integerReplacement(n) = integerReplacement(n / 2) + 1
溢出处理:本题的n上限为2^31 - 1,因此n+1很有可能溢出
A.最直接的处理方式是另写一个函数 int DFS(long long n)来替代 integerReplacement(int n)。
B.观察本题,当n为奇数时,n-1或者n+1,n-1与n+1都是偶数,因此下一步一定是/2,所以当n为奇数时,存在两种路线
1) integerReplacement(n / 2) + 2
2) integerReplacement(n / 2 + 1) + 2,这里是为了防止溢出,因此先除后加
class Solution {
public:
int integerReplacement(int n) {
if(n == 1) return 0;
if(n & 1){
return min(integerReplacement((n >> 1) + 1), integerReplacement(n >> 1)) + 2;
}else{
return integerReplacement(n >> 1) + 1;
}
}
};
思路二:对于上述的算法,还是会发生重复计算的问题,因此可以修改为记忆化DFS。
class Solution {
public:
unordered_map<int, int> hash_map;
int integerReplacement(int n) {
if(n == 1) return 0;
if(hash_map.find(n) != hash_map.end()) return hash_map[n];
if(n & 1){
return hash_map[n] = min(integerReplacement((n >> 1) + 1), integerReplacement(n >> 1)) + 2;
}else{
return hash_map[n] = integerReplacement(n >> 1) + 1;
}
}
};
[注]:复杂度分析,这里简单提一下,由于每一次DFS都有右移运算,因此可以简单推出时间复杂度是O(lgn)。
思路三:使用数学的方法分析
当 n 为偶数时,我们只有一种选择,就是使用一次操作将 n 变为 n / 2 ;
当 n 为奇数时,n 除以 4 的余数要么为 1,要么为 3 (为2就与n是奇数矛盾了)。
A.如果余数为 1,我们可以断定,应该将 n 变成 。如果我们必须将 n 变成
才能得到最优解,而
是奇数,那么:
[注]:若n%4==1,则我们可以将n表示为n=4*p+1,此时我们计算可知
因此说是奇数。由于是奇数,因此我们可以选择的下一步为-1或者+1
如果下一步进行 -1 再除以 2 (需要两个步骤),则得到
那么从 (n - 1) / 2 可以除以 2 (只需要一个步骤) 得到同样的结果;
如果下一步进行 +1 再除以 2 (需要两个步骤),则得到
那么从 (n - 1) / 2 可以除以 2 再 +1 (需要两个步骤) 得到同样的结果。
因此当n%4==1时,将 n 变成 (n−1) / 2 再往后走所需要的步骤数量总是不会多于将n变成(n + 1) / 2往后走需要的步骤数量。
B.如果余数为 3,我们可以断定,应该将 n 变成 (n + 1) / 2。如果我们必须将 n 变成(n−1) / 2才能得到最优解,而 (n−1) / 2 是奇数,那么:
如果下一步进行 -1 再除以 2,得到 (n−3) / 4,那么从 (n+1) / 2 可以除以 2 再 -1 得到同样的结果。
如果下一步进行 +1 再除以 2,得到 (n+1) / 4,那么从 (n+1) / 2 可以除以 2 得到同样的结果。
因此当n%4==3时,将 n 变成 (n+1) / 2 再往后走所需要的步骤数量总是不会多于将n变成(n−1) / 2往后走需要的步骤数量。
然而需要特别注意的是,如果当前是3,那么(n-1) / 2已经可以得到1,此时不应遵循上述规则。
class Solution {
public:
int integerReplacement(int n) {
int steps_num = 0;
while(n != 1){
if(n & 1){
if(n % 4 == 1){
n >>= 1;
}else{
if(n == 3){
n >>= 1;
}else{
n = (n >> 1) + 1;
}
}
steps_num += 2;
}else{
n = n / 2;
steps_num += 1;
}
}
return steps_num;
}
};