进制转换及位运算

目录

常见进制及相互转换方法

整数十进制转换为其他进制

小数十进制转换为其他进制

常用进制转换为十进制

二进制转八/十六进制

八进制转二/十六进制

十六进制转二/八进制

反码与补码

位运算

判断奇偶

交换两个数

找出没有重复的数

重复数字出现两(偶数)次

重复数字出现三(奇数)次

找全出现一次的数字

利用位运算计算1到n的和 

二进制中1的个数

面试题65. 不用加减乘除做加法


常见进制及相互转换方法

以下内容均出自下面链接,本文只是做一个分类总结

计算机基础进制转换(二进制、八进制、十进制、十六进制) https://blog.csdn.net/yuanxiang01/article/details/82503568

进制转换方法总结——摘自百度https://www.cnblogs.com/anke-z/p/12293818.html

百度:https://jingyan.baidu.com/article/495ba84109665338b30ede98.html

十进制向其他常用进制转换的过程,都是相似的:

整数十进制转换为其他进制

整数十进制转换为其他进制,流程都很相似,转化为什么进制,不断短除,取余数,序取值

   

(图源:百度)

小数十进制转换为其他进制

 


小数十进制转换为其他进制,不断乘积,取整数部分,序取值

转二进制

转八进制

**解析:**如下图所示,整数部分除以8取余数,直到无法整除。小数部分0.68乘以8,取整,然后再将小数乘以8,取整,直到达到题目要求精度。得到结果:12.534Q.

 

(以上内容出自:https://blog.csdn.net/yuanxiang01/article/details/82503568

非常感谢大佬分享!

常用进制转换为十进制

按位作幂,最终相加即可

 

(以上内容出自:https://blog.csdn.net/yuanxiang01/article/details/82503568

二进制转八/十六进制

二进制转换成八进制的方法是,取三合一法,即从二进制的小数点为分界点,向左(或向右)每三位取成一位

与二进制转八进制方法近似,八进制是取三合一,十六进制是取四合一

(注意事项,4位二进制转成十六进制是从右到左开始转换,不足时补0)。

八进制转二/十六进制

 八进制数通过除2取余法,得到二进制数,对每个八进制为3个二进制,不足时在最左边补零

八进制转十六进制 

第一种:他们之间的转换可以先转成二进制然后再相互转换。

第二种:他们之间的转换可以先转成十进制然后再相互转换。

 

十六进制转二/八进制

十六进制数通过除2取余法,得到二进制数,对每个十六进制为4个二进制,不足时在最左边补零。

(以上内容出自:https://jingyan.baidu.com/article/495ba84109665338b30ede98.html) 

反码与补码

 

反码:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外

1   原码0000 0001  反码0000 0001
-2   原码1000 0010  反码1111 1101
-1   原码1000 0001  反码1111 1110

 

补码:正数和+0的补码是其原码,负数则先计算其反码,然后反码加上1,得到补码

1   原码0000 0001  反码0000 0001  补码0000 0001
-2   原码1000 0010  反码1111 1101  补码1111 1110
-1   原码1000 0001  反码1111 1110  补码1111 1111

真值为-10 0101的数在字长为8的机器中的补码:我们先补全内容10  10 0101

原码:1010 0101

反码:1101 1010

补码:1101 1011

 

相关题目:

unsigned char *p1;
unsigned long *p2;
p1=(unsigned char *)0x801000;
p2=(unsigned long *)0x810000;

请问p1+5= 什么?
p2+5= 什么?

答案:

801005 810014

 

p1指向字符型,一次移动一个字符型,1个字节;p1+5后移5个字节,16进制表示为5;

p2指向长整型,一次移动一个长整型,4个字节,p2+5后移20字节,16进制表示为14。

 

在计算机中,整形用补码的形式存在,正数的补码就是本事,而负数的补码,是负数反码加1:

执行"int x=1;int y=~x;"语句后,y的值为?

假设int占2个字节,那么1的二进制表示是 0000 0001 ,~表示按位取反,则 0000 0001变为 1111 1110,在计算机中整数用补码形式表示,正数的补码是它本身,负数的补码是原数值除符号位按位取反再加一,由补码求原数值也是按位取反再加一,那么 1111 1110 除符号位按位取反再加一变成 1000 0010,即 -2。

(牛客网:@CZ❤♡ღQM

 

 

位运算

位运算(&、|、^、~、>>、<<)https://www.cnblogs.com/yrjns/p/11246163.html

【技巧总结】位运算装逼指南:https://blog.csdn.net/m0_37907797/article/details/103120886

位运算总结(按位与,或,异或) https://blog.csdn.net/sinat_35121480/article/details/53510793

 项目对左移和右移进行进一步的解释

     qDebug()<<(3<<1);
     qDebug()<<(3>>1);

 

 

以下内容参考:@帅地https://blog.csdn.net/m0_37907797/article/details/103120886

判断奇偶

上文中,我们提及了十进制和二进制之间的转换,那么对于十进制偶数来说,第一次短除2,余数一定是0;

对于十进制奇数来说,第一次短除2,余数一定是1;

那么,我们将十进制的数值与操作1,及&1

如果等于1,那么只可能是1&1,说明是奇数,反之为偶数:

    int n = 4;
    if((n & 1) == 0)//一定要加括号,因为==优先级更高
        qDebug()<<"偶数";
    else
        qDebug()<<"奇数";

交换两个数

异或运算是支持交换律的

我们先看以下代码:

    int x = 3,y = 5;
    x = x^y;
    y = x^y;
    x = x^y;

把(1)中的 x 带入 (2)中的 x,有

y = x^y = (xy)y = x(yy) = x^0 = x。 x 的值成功赋给了 y。

对于(3),推导如下:

x = x^y = (xy)x = (xx)y = 0^y = y。

以上推导来源:https://blog.csdn.net/m0_37907797/article/details/103120886

上面代码输出:

下面我们来逐步解析以下整个过程

x = x^y;

y = x^y;

 

x = x^y;

 

从整个过程中我们可以看到,其实就是一个交换的过程

如果遇到面试,要求在不使用额外空间的情况下,交换两个数字,位运算会是很好的选择。 

找出没有重复的数

leetcode位运算有一系列题目,非常经典,就是“只出现一次的数值”系列,下面我们来看看

重复数字出现两(偶数)次

136. 只出现一次的数字 https://leetcode-cn.com/problems/single-number/

例如这组数据是:1, 2, 3, 4, 5, 1, 2, 3, 4。其中 5 只出现了一次,其他都出现了两次,把他们全部异或一下,结果如下:

由于异或支持交换律和结合律,所以:

123451234 = (11)(22)(33)(44)5= 00005 = 5。

也就是说,那些出现了两次的数异或之后会变成0,那个出现一次的数,和 0 异或之后就等于它本身。

以上推导来源:https://blog.csdn.net/m0_37907797/article/details/103120886

https://leetcode-cn.com/problems/single-number/solution/zhi-chu-xian-yi-ci-de-shu-zi-by-leetcode-solution/

leetcode官方也是给出了非常详细的解答

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int temp = 0;
        for(int i=0; i<nums.size(); i++){
            temp ^= nums[i];
        }
        return temp;
    }
};

那么上题目要求,重复数字出现两次,也就是重复数字出现的次数是偶数次,通过这道题我们可以知道,只要是出现偶数次,那么都可以通过全部异或的方式找到出现一次的数值,但是奇数次呢?如果重复数字出现的次数是奇数次,那么我们又该如何应对?

重复数字出现三(奇数)次

137. 只出现一次的数字 II https://leetcode-cn.com/problems/single-number-ii/

面试题56 - II. 数组中数字出现的次数 II https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/

方法一:我们按位统计1出现的个数,一旦能被三整除(N%3 == 0),表示该数字一定出现了三次,我们不进行处理

一旦不能被三整除,那么余数只可能是1,我们将这个1所在的高低位进行记录。

通过从高位到低位的不断遍历,记录下出现一次数字从高位到低位出现1的位置,那么我们也就相当于得到了目标值。

下面是实现代码的主逻辑,所有元素的低位全部与1操作,然后累加,查看是否能把3整除,再进行后续操作。

然后所有元素的值全部右移一位,整个过程记录的都是最低位1的情况,通过循环不断右移元素。

Krahets对这个部分进行了比较详细的图解,下面是部分图解

原文地址:https://leetcode-cn.com/problems/single-number-ii/solution/single-number-ii-mo-ni-san-jin-zhi-fa-by-jin407891/

具体代码如下: 

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int n = nums.size();
        if(n == 0) return 0;
        int Res = 0;
        for(int i = 0;i<32;++i)
        {
            int numofone = 0;
            for(auto item:nums)
            {
                numofone += (item>>i)&1;//统计所有数字指定位的1的个数
            }
            Res = Res|( (numofone%3)<<i );//判断是不是能被3整除,如果不能,那么需要保存,那么循环内右移了多少,现在就左移回来

        }
        return Res;

    }
};

代码参考:https://leetcode-cn.com/problems/single-number-ii/solution/zi-dong-ji-wei-yun-suan-zui-xiang-xi-de-tui-dao-gu/

方法二:方法一总归还是有些笨拙,我们看看还有没有更好的解法


TankCode从逻辑电路的角度对本题进行了分析

原文:https://leetcode-cn.com/problems/single-number-ii/solution/luo-ji-dian-lu-jiao-du-xiang-xi-fen-xi-gai-ti-si-l/

思路如下:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int once = 0, twice = 0;
        for (auto x : nums) {
            once = (once^x)&(~twice);
            twice = (twice^x)&(~once);
        }
        return once;
    }
};

// 作者:godweiyang
// 链接:https://leetcode-cn.com/problems/single-number-ii/solution/zi-dong-ji-wei-yun-suan-zui-xiang-xi-de-tui-dao-gu/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本题第二种解法可谓巧夺天工。 

当你在leetcode上刷了越来越多的题目,你会发现很多题是数学题,但是怎么也想不到还有模电题

逻辑运算其实在工业中应用中很广泛,不需要额外空间,稳定,快速,好理解。

找全出现一次的数字

面试题56 - I. 数组中数字出现的次数 https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/

面试题 17.19. 消失的两个数字 https://leetcode-cn.com/problems/missing-two-lcci/

260. 只出现一次的数字 III https://leetcode-cn.com/problems/single-number-iii/

那么,当我们不知道位运算的时候,看到以上的题目,首先想到的是Hash表,建立一个出现次数和数值对应的Hash表。

下面我们拿Hash表对260进行解答:

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int size = nums.size();
        vector<int> Res;
        if(size == 0) return Res;
        unordered_map<int,int>M;//Hash表
        for(int i = 0;i<size;++i)
            M[nums[i]]++;
        for(int i = 0;i<size;++i)
            if(M[nums[i]] == 1) Res.push_back(nums[i]);

        return Res;
    }
};

Hash表的时间和空间复杂度就要高一些了

  • 时间复杂度:O(N)。
  • 空间复杂度:O(N)

我们有什么办法提高空间复杂度呢?

除此之外,还有什么办法呢?我们如果能讲整个数组分为多个部分,各个部分都有一个出现了一次的数字,其余都是出现了两次的数字,那么问题就转换为了136。本题难点,就是如何进行分割。

class Solution {
public:
    //输入: [1,2,1,3,2,5]
    //输出: [3,5]
    vector<int> singleNumber(vector<int>& nums) {
        int s = 0;
        for (int num : nums) {
            s ^= num;
        }
        //s是只出现一次的2个数字的^ 记做数字a,b
        //既然a,b 不一样,那么s肯定不是0,那么s的二进制肯定至少有1位(第n位)是1,只有0^1才等于1
        //所以a,b 在第n位,要么a是0,b是1 ,要么b是0,a是1    ---->A
        //s = 3 ^ 5; 0011 ^ 0101 = 0110 = 6
        //假设int是8位
        //-6  原码1000 0110
        //    反码1111 1001
        //    补码1111 1010
        //s & (-s)
        //  0000 0110
        //& 1111 1010
        //  0000 0010
        //所以k = s & (-s) 就是保留s的最后一个1,并且将其他位变为0  也就是s最后一个1是倒数第二位   --->B
        //由于s & (-s)很方便找到一个1 所以用他了,其实找到任何一个1都可以
        //根据A和B  我们可以确定 3 和 5 必定可以分到 不同的组里
        //同理 1和1 由于二进制完全相同,所有必定分到相同的组里
        int k = s & (-s);
        //1  0001  第一组
        //2  0010  第二组
        //1  0001  第一组
        //3  0011  第二组
        //2  0010  第二组
        //5  0101  第一组
        //第一组 1 1 5  第二组 2 3 2 这样我们就将2个只有一个的数 分到了2个数组里了
        vector<int> rs(2,0);
        for(int num : nums){
            if(num & k){
                //第二组
                rs[0] ^= num;
            }else{
                //第一组
                rs[1] ^= num;
            }
        }
        return rs;
    }
};

// 作者:chuang-bian-gu-shi
// 链接:https://leetcode-cn.com/problems/single-number-iii/solution/c-0ms-chao-ji-xiang-xi-jie-shi-kan-bu-dong-de-jiu-/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 方法可谓鬼才解法。

分组和核心是什么呢?就是这两个只出现过一次的数字,a和b,异或结果一定不等于0

那么,假设,a^b = c;

c为 0000 0100 那么在1的这个位置,要么a在该位为0,b在该位为1,要么a在该位为1,b在该位为0

if a在该位为0,b在该位为1

那么a&c  = 0;b&c != 0,反之成立,那么分组就分好了。

我们找到a^b的结果,随便找一个位置的1,作为参数K,然后遍历整个数组,所有元素都和这个参数K进行与操作,等于0的分为一组,不等于0的分为另外一组,以上的参考程序使用的是补码形式,下面我们换一种:

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int size = nums.size();
        // if(size == 0) return 0;
        int s = 0,k = 0;
        for(auto item:nums) s^=item;
        for(int i = 0;i<32;++i)
        {
            int temp = (s>>i)&1;
            cout<<"temp"<<temp<<endl;
            if(temp == 1) {k = k|(1<<i);break;}//此处要是1,因为我们只需要一个1,不需要其他的1
        }
        cout<<k<<endl;
        int res1 = 0,res2 = 0;
        for(auto item:nums)
        {
            if((item&k) == 0)//务必注意优先级 
            {res1 = res1^item;}
            else {res2 = res2^item;}    
        }
        vector<int> Res;
        Res.push_back(res1);
        Res.push_back(res2);

        return Res;

    }
};

利用位运算计算1到n的和 

面试题64. 求1+2+…+n  https://leetcode-cn.com/problems/qiu-12n-lcof/

内容参考:https://leetcode-cn.com/problems/qiu-12n-lcof/solution/mian-shi-ti-64-qiu-1-2-nluo-ji-fu-duan-lu-qing-xi-/

 

看见本题的第一个反应,就是递归进行计算,用递归来取代for循环的迭代

那么我们很容易写出下面的程序:

当n等于0的时候,停止递归

class Solution {
public:
    int sumNums(int n) 
    {
        if(n == 0) return 0;
        n += sumNums(n-1);
        //逻辑运算
        // n && (n+=sumNums(n-1));
        return n;
    }
};

下一步,我们利用&&逻辑运算符,对if语句进行替换

A&&B,当A为false,程序就不用再指向B语句,因为&&运算,一个为假全部为假

我们需要在程序中停止递归,那么我们这么写:

n && (n+=sumNums(n-1));

当n == 0,这条语句就完全结束,不会指向后面的内容

全部程序如下:

class Solution {
public:
    int sumNums(int n) 
    {
        n && (n+=sumNums(n-1));
        return n;
    }
};

二进制中1的个数

面试题15. 二进制中1的个数

https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/

直接使用1进行与运算,是非常好的方法

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int Res = 0;
        for(int i = 0;i<32;++i)
        {
            if(n&(1<<i)) Res++;
        }
        return Res;
    }
};

那么有没有更好的办法呢?

方法二:巧用 n&(n−1)

 

 

n-1会让二进制n最右侧的第一个1,变成0,第一个1右侧的0全部变成1

那么我们进行与操作,并计数,然后让n = n&(n-1)进行迭代,下面我们看完整版本的程序:

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int ret = 0;
        while (n != 0) {
            n &= n-1;
            ret ++;
        }
        return ret;
    }
};

 

内容参考:https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/solution/mian-shi-ti-15-er-jin-zhi-zhong-1de-ge-shu-wei-yun/

下面还有两道题目,用到了类似的方法:

461. 汉明距离

https://leetcode-cn.com/problems/hamming-distance/

暴力解法:

class Solution {
public:
    int hammingDistance(int x, int y) {
        //暴力,每一位都比较一次
        int Res = 0;
        for(int i = 0;i<32;++i) if((x&(1<<i)) != (y&(1<<i))) Res++;
        return Res;
    }
};

布赖恩·克尼根算法:

class Solution {
public:
    int hammingDistance(int x, int y) {
        int xor1=x^y;
        //相同为0,不同为1,先异或,变成一个数值
        int distance = 0;
        while(xor1)
        {
            distance++;
            xor1 = xor1&(xor1-1);
        }
        return distance;
    }
};

 

338. 比特位计数 

https://leetcode-cn.com/problems/counting-bits/

和我们之前做过的题目很像,只不过一个是算一个数字的,本题是算一个范围内容数字的

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int>Res;
        Res.push_back(0);
        for(int i = 1;i<=num;++i) Res.push_back(CalZero(i));
        return Res;
    }
    int CalZero(int num)
    {
        int size = 0;
        while(num)
        {
            num &= (num-1);
            size++;
        }
        return size;       
    }
};

本题靠直觉,都知道有更快的方法:利用前一个数字的二进制中1的个数,推算出后一个数字中二进制1的个数

利用动态规划完成:

dp就是表示数字i的二进制中,1的个数

那么我们列出部分数字,看看效果:

 

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int>dp(num+1,0);
        dp[0]=0;//base case
        for(int i = 1;i<=num;++i)
        {
            if(i%2!=0)//奇数
            dp[i] = dp[i-1]+1;
            else 
            dp[i] = dp[i/2];//偶数
        }
        return dp;
    }
};

算法参考:https://leetcode-cn.com/problems/counting-bits/solution/hen-qing-xi-de-si-lu-by-duadua/

面试题65. 不用加减乘除做加法

https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/

下面的文章解释的非常清晰:

 

https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/solution/jin-zhi-tao-wa-ru-he-yong-wei-yun-suan-wan-cheng-j/

我们再一起复现一下整个过程:

假设都是正数,我们加和,考虑进位和没有进位下的和,如图所示

完成上面的步骤后,我们下一步是什么操作呢?把{进位}{不考虑进位的和}相加。这不也就成了a+b吗?所以是一个不断递归的过程。

我们将a重新赋值,变成{进位},b重新赋值,变成{不考虑进位的和}

再次进行加法操作,再次赋值后,a==0,循环结束,b就是我们求的结构

class Solution {
public:
    int add(int a, int b) {
        int temp = 0;
        while(a)
        {
            temp = (a&b)<<1;
            b ^= a;//异或
            a = temp;
        }
        return b;
    }
};

负数处理方法:

我们目前只考虑是正数,那么负数我们应该怎么处理呢?和上面是一样的,记住,负数的补码是反码加1

但是 c++ 不允许负数左移操作,所以要转换成无符号整数(unsigned int)

temp = (unsigned int)(a&b)<<1;

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值