九日集训 day one
leetcode371 不用加号的加法器
题目描述:
Given two integers a and b, return the sum of the two integers without using the operators + and -.
leetcode371
要求使用solution方法解决
cpp代码:
class Solution {
public:
int getSum(int a, int b) {
while (b != 0) {
unsigned int carry = (unsigned int)(a & b) << 1;
a = a ^ b;
b = carry;
}
return a;
}
};
代码解读:
先看循环条件:b!=0
再看循环主体:先对a&b的结果进行左移计算再用a^b进行异或运算,b=carry之后就进入循环
这么做在我看来非常巧妙,与运算确定当前的进位,异或运算确定没有进位的加法。
这里我们直接举两个例子
例一
初始输入:a=0010 b=0011
第一遍循环:
首先判断b!=0
carry=>0100
a=>0001(无进位加法)
b=>carry=>0100
第二遍循环:
首先判断b!=0
carry=(a&b<<1)=>0000
a=a^b=>0101
b=0000
第三遍循环:
首先判断b==0那么跳出循环
例二
初始输入:a=0111 b=0001
这里考虑的是连续进位,体现的是循环条件设置的巧妙(b!=0说明还有进位的可能)
第一遍循环
b!=0
c=0010
a=0110
b=c=0010
第二遍循环
b!=0
c=0100
a=0100
b=c=0100
第三遍循环
b!=0
c=1000
a=0000
b=0100
第四遍循环
b!=0
c=0000
a=0100
b=0000
第五遍循环
b==0跳出循环
leetcode2119翻转两次的数字
问题描述
代码
class Solution {
public:
bool isSameAfterReversals(int num) {
if(num==0){
return true;
}
if(num%10==0){
return false;
}else{
return true;
}
}
};
实际上很简单,如果最后一位是0,那么两次翻转之后肯定不一样的。
如果最后一位不是0或者本身就是0,那么翻转过后是一样的
leetcode 面试题08.05
题目描述和题目链接
代码和解读
class Solution {
public:
int multiply(int A, int B) {
return sizeof(char[A][B]);
}
};
考虑将A乘以B看作是计算宽度为A、高度为B的矩阵中的单元数。
leetcode29除数与被除数
题目描述和题目链接
代码和讲解
class Solution {
public:
int divide(int dividend, int divisor) {
int flag = 0;
int res = 0;
long dividend1 = dividend;
long divisor1 = divisor;
if (dividend1 == -2147483648 && divisor1 == -1)
{
return 2147483647;
}
if (divisor1 == 1)
{
return dividend;
}
if (divisor1 == -1)
{
return 0 - dividend;
}
if ((dividend1 > 0 && divisor1 < 0) || (dividend1 < 0 && divisor1>0))
{
flag = 1;
}
if (dividend1 < 0)
{
dividend1 = -dividend1;
}
if (divisor1 < 0)
{
divisor1 = -divisor1;
}
while (dividend1>0)
{
res++;
dividend1 = dividend1 - divisor1;
}
if (dividend1 == 0&&flag==1)
{
return -res;
}
else if (dividend1 == 0 && flag == 0)
{
return res;
}
else if (flag == 1)
{
return 0 - (res - 1);
}
return res - 1;
}
};
这段代码实现了整数除法,即求两个整数的商,不使用乘法、除法和模运算符。这一限制意味着不能直接用简单的算术操作符来获得结果。因此,作者采用了连续减法的方法来逼近商的值。这种方法的核心思想基于如下观察:
连续减法
连续减法的基本思想是:从被除数中连续减去除数,直到差值小于除数为止。每减一次,商增加一。例如,计算10除以3的商时,我们从10开始,连续减去3(10-3=7, 7-3=4, 4-3=1),减了三次后余数为1,小于3,所以商为3。
处理特殊值
- 溢出处理:当被除数是
INT_MIN
(-2147483648)且除数是-1
时,直接计算会导致整数溢出,因为结果2147483648超出了整数的最大值2147483647。为了处理这种特殊情况,代码返回INT_MAX
。 - 除数为1或-1:当除数是1或-1时,商等于被除数或被除数的相反数,这两种情况都可以直接计算,无需进一步操作。
符号处理
由于整数除法需要处理负数,代码首先通过检查被除数和除数的符号来设置一个标志(flag
),用来确定最终结果的符号。如果符号相同(都是正数或都是负数),商是正数;如果符号不同,则商是负数。然后,为简化计算,将被除数和除数都转换为正数进行运算。
结果校正
在完成所有减法操作后,需要根据余数(dividend1
)和符号标志(flag
)来校正和返回正确的商。如果减到最后余数为0,直接根据 flag
返回 res
或 -res
;如果余数不为0,说明整除没有完成,需要做适当的调整(通常是将计算得到的 res
减一,因为最后一次减法没有完全减去一个除数)。
总结
此代码通过简单且直接的连续减法逐步逼近商的值。这种方法在概念上简单易懂,但效率不高,特别是在大数除法中,需要很多次迭代才能达到结果。在工程实践中,通常会采用更高效的算法(如位移算法)来提高除法运算的性能。
leetcode面试16.07最大数值
题目链接与题目描述
代码讲解
这里涉及到一个位移运算的知识,我写在下面的知识点板块里面了
class Solution {
public:
int maximum(int a, int b) {
long sign=(long)a-(long)b;
int k=(int)(sign>>63);
return (1+k)*a-k*b;
}
};
为什么要使用long?因为怕相减的结果超过32位整数,所以要用64位整数来存储目标数值
k右移代表着什么?
如果是正数,右移63位则左边空出来的位置全部置零,最终剩下符号位:0,那么就是一个64位的0
如果是负数,右移63位则左边空出来的位置全部置1,最终剩下符号位:1,那么就是一个64位的1,也就是补码形式的-1。此时k=-1
那么最后return也是根据k的正负来的。
leetcode69平方根
题目描述和题目链接
代码讲解
这道题感觉其实比起之前的涉及到位运算的会让我好理解的多……因为涉及到位运算的需要我注意符号位和各种各样的位移运算,让我十分滴头疼啊。
直接上代码:
class Solution {
public:
int mySqrt(int x) {
if(x == 1)
return 1;
int min = 0;
int max = x;
while(max-min>1)
{
int m = (max+min)/2;
if(x/m<m)
max = m;
else
min = m;
}
return min;
}
};
这是通过二分法,让上下界不断靠近它最后输出下界
leetcode50 pow(x,n)快速幂方法
题目链接与题目描述
代码讲解
class Solution {
public:
double myPow(double x, int n) {
// 处理 n 为负数的情况,特别注意 n = INT_MIN 的情况
if (n < 0) {
x = 1 / x;
// 当 n = INT_MIN 时,-n 仍然是 INT_MIN,这里通过逐步计算来规避这个问题
return n == INT_MIN ? x * myPow(x, -(n + 1)) : myPow(x, -n);
}
double result = 1.0;
double base = x;
while (n > 0) {
if (n % 2 == 1) { // 如果当前指数的最低位是1
result *= base;
}
base *= base; // 底数自身平方
n /= 2; // 指数右移一位
}
return result;
}
};
知识点
int 的取值范围
我们常常看到int取值范围为-32768~32767,实际上int的取值范围依赖于计算机系统,在16位机器中,int占16位,其中一位为符号位,所以取值范围为前面所说的-32768~32767.
−
2
15
∼
2
15
−
1
-2^{15} \sim 2^{15}-1
−215∼215−1
但是在现代的32位机器和64位机器中,int占32位,其中一位为符号位。所以取值范围是-2147483648~2147483647
−
2
31
∼
2
31
−
1
-2^{31} \sim 2^{31}-1
−231∼231−1
如果题干要求不超过32位,正数的话,我们其实可以用unsigned int无符号整数来拓展int的容量
位运算
C语言中的位运算是一种在位级别上对整数进行操作的手段,它直接对二进制数的各个位进行逻辑运算。位运算比普通的算术运算通常要快,并且在某些情况下可以提供更简洁的代码实现。下面是C语言提供的几种位运算符:
-
按位与(AND)
&
- 用法:
c = a & b;
- 只有当
a
和b
对应位都为1时,结果c
的对应位才为1,否则为0。 - 例如:
5 & 3
(二进制为0101 & 0011
)得到1
(二进制为0001
)。
- 用法:
-
按位或(OR)
|
- 用法:
c = a | b;
- 只要
a
或b
的对应位中有一个为1,结果c
的对应位就为1。 - 例如:
5 | 3
(二进制为0101 | 0011
)得到7
(二进制为0111
)。
- 用法:
-
按位异或(XOR)
^
- 用法:
c = a ^ b;
- 当
a
和b
的对应位不同(一个为0,一个为1)时,结果c
的对应位为1;如果相同,则为0。 - 例如:
5 ^ 3
(二进制为0101 ^ 0011
)得到6
(二进制为0110
)。
- 用法:
-
按位非(NOT)
~
- 用法:
c = ~a;
- 将
a
的每一位取反:0变1,1变0。 - 例如:
~5
(5
的二进制为0101
,取反得1010
,在8位整数中表示为-6
)。
- 用法:
-
左移
<<
- 用法:
c = a << n;
- 将
a
的二进制位向左移动n
位,右边空出的位用0填充。 - 例如:
5 << 1
将5
(二进制为0101
)左移一位得到10
(二进制为1010
)。
- 用法:
-
右移
>>
- 用法:
c = a >> n;
- 将
a
的二进制位向右移动n
位。对于有符号整数,通常左边空出的位用符号位填充(算术右移),对于无符号整数,用0填充(逻辑右移)。 - 例如:
5 >> 1
将5
(二进制为0101
)右移一位得到2
(二进制为0010
)。
- 用法:
位移操作
-
左移 (
<<
):- 所有位向左移动指定的位数。
- 右边空出来的位用0填充。
- 左边超出的位被丢弃。
- 由于左边的位被丢弃,这可能导致整数的符号改变(即从正变负或从负变正),这种情况被认为是溢出。
-
右移 (
>>
):- 所有位向右移动指定的位数。
- 分为两种类型:
- 算术右移:用最左边的位(符号位)填充左边空出来的位置。这保持了数的符号不变。负数的符号位为1,所以左边补1;正数的符号位为0,所以左边补0。
- 逻辑右移:左边空出来的位总是用0填充,不论数的符号如何。这种移动主要用于无符号数。
在大多数编程语言中,整数的右移默认为算术右移,这意味着移动时保持数的符号不变。对于无符号数,通常使用逻辑右移。左移则没有这种区分,总是向左移动并在右侧补0。
这些操作在处理低级位操作或优化代码时非常有用,但也需要谨慎使用,以避免溢出和未定义行为。