目录
常见进制及相互转换方法
以下内容均出自下面链接,本文只是做一个分类总结
计算机基础进制转换(二进制、八进制、十进制、十六进制) 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
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对这个部分进行了比较详细的图解,下面是部分图解
具体代码如下:
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;
}
};
方法二:方法一总归还是有些笨拙,我们看看还有没有更好的解法
TankCode从逻辑电路的角度对本题进行了分析
思路如下:
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/
看见本题的第一个反应,就是递归进行计算,用递归来取代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的个数
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;
}
};
下面还有两道题目,用到了类似的方法:
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;
}
};
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/
下面的文章解释的非常清晰:
我们再一起复现一下整个过程:
假设都是正数,我们加和,考虑进位和没有进位下的和,如图所示
完成上面的步骤后,我们下一步是什么操作呢?把{进位}和{不考虑进位的和}相加。这不也就成了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;