九日集训day one|心猿意马

九日集训 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 2152151
但是在现代的32位机器和64位机器中,int占32位,其中一位为符号位。所以取值范围是-2147483648~2147483647
− 2 31 ∼ 2 31 − 1 -2^{31} \sim 2^{31}-1 2312311

如果题干要求不超过32位,正数的话,我们其实可以用unsigned int无符号整数来拓展int的容量

位运算

C语言中的位运算是一种在位级别上对整数进行操作的手段,它直接对二进制数的各个位进行逻辑运算。位运算比普通的算术运算通常要快,并且在某些情况下可以提供更简洁的代码实现。下面是C语言提供的几种位运算符:

  1. 按位与(AND)&

    • 用法:c = a & b;
    • 只有当 ab 对应位都为1时,结果 c 的对应位才为1,否则为0。
    • 例如:5 & 3(二进制为 0101 & 0011)得到 1(二进制为 0001)。
  2. 按位或(OR)|

    • 用法:c = a | b;
    • 只要 ab 的对应位中有一个为1,结果 c 的对应位就为1。
    • 例如:5 | 3(二进制为 0101 | 0011)得到 7(二进制为 0111)。
  3. 按位异或(XOR)^

    • 用法:c = a ^ b;
    • ab 的对应位不同(一个为0,一个为1)时,结果 c 的对应位为1;如果相同,则为0。
    • 例如:5 ^ 3(二进制为 0101 ^ 0011)得到 6(二进制为 0110)。
  4. 按位非(NOT)~

    • 用法:c = ~a;
    • a 的每一位取反:0变1,1变0。
    • 例如:~55 的二进制为 0101,取反得 1010,在8位整数中表示为 -6)。
  5. 左移 <<

    • 用法:c = a << n;
    • a 的二进制位向左移动 n 位,右边空出的位用0填充。
    • 例如:5 << 15(二进制为 0101)左移一位得到 10(二进制为 1010)。
  6. 右移 >>

    • 用法:c = a >> n;
    • a 的二进制位向右移动 n 位。对于有符号整数,通常左边空出的位用符号位填充(算术右移),对于无符号整数,用0填充(逻辑右移)。
    • 例如:5 >> 15(二进制为 0101)右移一位得到 2(二进制为 0010)。

位移操作

  1. 左移 (<<):

    • 所有位向左移动指定的位数。
    • 右边空出来的位用0填充。
    • 左边超出的位被丢弃。
    • 由于左边的位被丢弃,这可能导致整数的符号改变(即从正变负或从负变正),这种情况被认为是溢出。
  2. 右移 (>>):

    • 所有位向右移动指定的位数。
    • 分为两种类型:
      • 算术右移:用最左边的位(符号位)填充左边空出来的位置。这保持了数的符号不变。负数的符号位为1,所以左边补1;正数的符号位为0,所以左边补0。
      • 逻辑右移:左边空出来的位总是用0填充,不论数的符号如何。这种移动主要用于无符号数。

在大多数编程语言中,整数的右移默认为算术右移,这意味着移动时保持数的符号不变。对于无符号数,通常使用逻辑右移。左移则没有这种区分,总是向左移动并在右侧补0。

这些操作在处理低级位操作或优化代码时非常有用,但也需要谨慎使用,以避免溢出和未定义行为。

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值