在进行位操作算法设计之前,先了解位操作的一些细节知识点:
1. 位操作数据溢出的结果
2. 数据位提升的隐式转换
统计一个int类型整数对应的二进制数的1的个数:
int NumberOf1Bits(int n) { int cnt = 0; while(n != 0) { cnt++; n = n & (n - 1); } return cnt; }
分析一个这个程序,如果这个n是正数,没问题,最后肯定能到0,终止循环。但是对于一个负数,它能终止退出循环吗?答案是可以的。因为这个负数一个跟n-1做按位与操作,那么1的个数会一直减少,知道最后变成10000...0000这样的形式,也就是int能表示的最小的负数,n-1得到的结果就是0111...111变成了最大的正数,两个东西按位与得到的结果就是零,这个样子就结束了循环。
这个问题还有另外的一种解法,那就是移位:
int NumberOf1Bits(int n) { int cnt = 0; for(int i = 0; i < 32; i++) { cnt += (n & 1); n = (n >> 1); } return cnt; }
判断一个数是不是2的幂:
这个题目首先把负数排除在外,一个负数绝对不是2的幂。在来总结2的幂的数的特征:
1 2 4 8 16 ....
1 10 100 1000 10000 ....
特征就是在某一位中只有一个1,所以可以统计这个数字的1的个数,判断是不是2的幂。
bool isPowerOfTwo(int n) { int cnt = 0; while(n > 0) { cnt++; n = n & (n - 1); } return cnt == 1; }
当然利用移位的方式计算1的个数一样可行,这里还有一个简单的方法。看这些数字的特征是都只有一个1,那么它们和n-1按位与的结果一定是0。所以:
bool isPowerOfTwo(int n) { return (n > 0) && !(n & (n - 1)); }
逆序一个无符号整数数的各个bit位,得到一个新的无符号数:
uint32_t reverseBits(uint32_t n) { uint32_t res = 0; for(int i = 0; i < 32; i++) { if(n & 1 == 1) { res = (res << 1) + 1; }//if else { res = res << 1; }//else n = n >> 1; }//for return res; }
这个算法就是利用了按位与的相关知识点。
逆序一个整型int:
算法的思想类似bit位的逆序,但是要考虑好负数与数据溢出的风险。
int reverse(int x) { int d = 0; int res = 0; while(x != 0) { if(abs(res) > INT_MAX / 10) return 0; d = x % 10; res = res * 10 + d; x = x / 10; } return res; }
由于负数在求mod和除法的时候,符号是保留不变的,所以不用单独的考量负数的问题。但是在检验数据溢出的时候,不能等溢出了在去检测,要在还未溢出但是下次就要溢出时检测,返回0。
不用四则运算的加法
这里我们首先得到一个规律:对于数字的运算,除了四则运算,就是位运算了。
题目描述:
给出两个整数a和b, 求他们的和, 但不能使用 + 等数学运算符。
不能用四则运算,那么就只剩下位运算了。
接下来借鉴计算机组成原理的加法运算器的工作原理来看。真正的加法运算可以分解为三个步骤:
1. 不考虑进位的加法运算,当然这种运算可以用按位异或运算代替。 2. 计算每一位的进位值,因为每一个进位都是往高位进一位,所以向左移动一位。 3. 把上面得到的两个结果相加,类似于一个递归的过程。
所以代码实现:
int aplusb(int nums1, int nums2) { // write your code here, try to do it without arithmetic operators. int sum = 0; int carry = 0; do { sum = nums1 ^ nums2; carry = (nums1 & nums2) << 1; nums1 = sum; nums2 = carry; }while(nums2 != 0); return nums1; }