JZ15 二进制中1的个数
题面
思路1
n与n-1与运算,比如136=10001000,135=10000111
两数相与之后就减少了一个1,把遇到的1计数
class Solution {
public:
int NumberOf1(int n) {
int cnt=0;
while(n) {
n = n&(n-1);
cnt++;
}
return cnt;
}
};
思路2
和1按位与
class Solution {
public:
int NumberOf1(int n) {
int cnt=0, k=1;
while(k) {
if(n&k) {
cnt++;
}
k <<= 1; //左移
}
return cnt;
}
};
JZ65 不用加减乘除做加法
题面
思路1
- 非递归方案
- 两个数字相加的时候,我们先将两个数字视为二进制
- 与运算产生进位的方案,之后左移1位就是每轮需要进位的方案
- 或运算可以产生非进位的加和结果,因此每轮都要加和一次两个数字,视是否有进位来决定是否要下一轮继续循环
分布图解
复杂度分析1
- 时间复杂度:O(log(max(num1,num2))),考虑二进制之后在原数字的基础上求了对数,循环的次数也和二进制位数相关
- 空间复杂度:O(1),没有额外引入空间
代码1
class Solution {
public:
int Add(int num1, int num2) {
int sum = num1; //和
int add = num2; //进位值
while(add != 0) { //进位为0时,退出循环
int temp = sum^add; //不考虑进位的和(新设变量延长赋值,防止对下面的add产生影响)
add = (sum & add) << 1; //进位值
sum = temp;
}
return sum; //没有进位时,返回总和
}
};
思路2
- 递归方案(很直接的思路)
- 以num2承接是否有进位的工作,num1作为加和的结果
- 首先判断num2是否有进位
-
如果有进位则递归调用函数,并将num1更新为或运算的结果,num2更新为与运算左移一位的结果
-
如果无进位则返回num1,因为num1一直在记录加和结果
-
复杂度分析2
- 时间复杂度:O(log(max(num1,num2))),考虑二进制之后在原数字的基础上求了对数,循环的次数也和二进制位数相关
- 空间复杂度:O(log(max(num1,num2))),递归栈的深度
代码2
class Solution {
public:
int Add(int num1, int num2) {
// 递归求和,也是判断是否有进位值,此时进位值用num2来表示,每轮的结果都存储在num1中
return num2 ? Add(num1 ^ num2, (num1 & num2) << 1) : num1;
}
};
JZ64 求1+2+3+...+n
题面
思路1
- 不能循环,就递归实现连加(到0时停止递归);
- 不能使用选择/判断语句,就用与运算的短路操作:函数中,与运算成立才继续。否则函数终止返回false
复杂度分析1
- 时间复杂度:O(n),一共递归n次
- 空间复杂度:O(n),递归栈深度为n
代码1
class Solution {
public:
int Sum_Solution(int n) {
n && (n += Sum_Solution(n-1)); //与运算代为判断n是否为整数
return n;
}
};
思路2
- 计算内存:通过公式和二维数组的空间大小结合起来
- 等量关系:1+2+3+...+n = (n+1)∗n/2 = sizeof(a[n][n+1])/2 = sizeof(a)>>1
- 其中数组a我们可以设置为bool型,只占一个空间,因此二维数组所占空间的一半就是我们要求的值。
复杂度分析2
- 时间复杂度:O(1),直接计算,常数时间
- 空间复杂度:O(n^2),辅助数组a的空间大小
代码2
class Solution {
public:
int Sum_Solution(int n) {
bool a[n][n+1];
return sizeof(a)>>1;
}
};
思路3
快速乘法
计算n(n+1)>>1n(n+1)>>1n(n+1)>>1即可。乘法不允许被使用,我们可以用快速乘法的加法来代替,快速乘法如下:
图解3
复杂度分析3
- 时间复杂度:O(1),常数时间
- 空间复杂度:O(1),常数空间
代码3
换成加法运算以后的代码为:
int fast(int x, int y){ //快速乘法
int res = 0;
while(y){
if(y & 1){
res += x;
}
y = y >> 1;
x = x << 1;
}
return res;
}
这个算法中就全是加法和移位算法,但是还有循环和判断,判断我们可以用与运算解决,循环其实就是数字的位数,我们这里数字不会超过14位,因此我们将循环拆开直接写14次。
class Solution {
public:
int res = 0;
int Sum_Solution(int n) {
int res = 0;
int A = n, B = n + 1;
//14次快速乘法
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
(B & 1) && (res += A);
A <<= 1;
B >>= 1;
return res >> 1; //除2
}
};
JZ16 数值的整数次方
题面
思路
- 快速幂,思路就是增大底数减少循环次数。
- 下面用^表示指数运算,讨论a^b非负指数情况:
- 当b为偶数时,a^b = (a^2)^(b/2)
- 当b为奇数时,a^b = a(a^2)^(b/2)
- 程序实现时,奇偶判断可以用位与运算,除2可以用位移运算,负指数转化成正指数。 如果用递归实现看起来会更直观,下面用循环实现。
代码
class Solution {
public:
double Power(double base, int exponent) {
int tag = 0;
if (exponent < 1) {
exponent *= -1;
tag = 1;
}
double ret = 1;
while (exponent) {
if (exponent & 1) ret *= base;
base *= base;
exponent >>= 1;
}
if (tag) ret = 1 / ret;
return ret;
}
};
待添加...