如果你也在为数据结构感到苦恼,那就和我一起刷穿🗡☞
想要最好的生活,那就先让生活看到最好的你
目录
二进制的位运算:与、或、非、异或、左移、右移。
与(&) | 或(|) | 异或(^) |
0&0=0 | 0|0=0 | 0^0=0 |
1&0=0 | 1|0=1 | 1^0=1 |
0&1=0 | 0|1=1 | 0^1=1 |
1&1=1 | 1|1=1 | 1^1=0 |
左移运算符:左移n位,左边n位被丢弃,右边补n个0。
右移(右移需要正负):正数右移,右边丢弃,左边补0;负数右移,右边丢弃,左边补1。
Java中增加无符号右移操作符(">>>"):无论正数还是负数,都在最左边插入0。
1、只出现一次的数字
【题目描述】找出数组中只出现了一次的数字。(其它数字都出现了三次)
分析:
之前有一个简化版本的“数组中只有一个数组出现一次,其余的都出现两次”。任何一个数组异或自己的结果都为0.则将数组中所有的数字进行异或,最终的结果就是我们要找的这个数字。
但是,对于这个题来说,其它数字都出现三次,则无法使用异或运算来进行计算。
一个整数是由32个0或1组成。我们可以将数组中所有元素的同一个位置的数位相加,如果第i位相加之和可以被3整除(因为数组中只有一个元素出现一次,其余的出现三次,所以余数只能是0或1),那么只出现一次的数字的第i位一定是0;如果不能被三整除,则只出现一次的数字第i位为1。这样我们就可以推出只出现一次的数字的所有二进制位,进而知道它的值。
【代码如下】
class Solution {
//只有一个数出现一次(x),其余的出现三次,则将其转换为二进制,按位相加,如果其位能整除3,则x该二进制位为0;否则该二进制位为1
public int singleNumber(int[] nums) {
int[] array=new int[32];
for(int j=0;j<nums.length;++j){
for(int i=0;i<32;++i){
//获取数组中下标为j的元素的从前往后数第i位的值
array[i]+=nums[j]>>(31-i)&1;
}
}
int ret=0;
for(int i=0;i<32;++i){
//此时array数组中存储的是只出现一次的数组的二进制的每一位
ret=(ret<<1)+(array[i]%3==0?0:1);
}
return ret;
}
}
【拓展】这个方法可以计算查找中唯一一个出现m次的数字
输入一个整数数组,数组中只有一个数组出现m次,其他数字出现n次。找出那个出现m次的数字(m不能被n整除)。
【分析】如果数组中所有数字的二进制数的第i位相加之和能被n整除,那么出现m次的数字第i位一定为0;否则出现m次的数字第i位位1;
2、前n个数二进制形式中1的个数
【题目描述】输入一个非负数n,计算0到n之间每个数字的二进制形式中1的个数,输出为一个数组。
【分析】可以使用一个for循环来遍历从0到n这n个数。问题转化为如何求一个整数二进制中1的个数。(以下介绍的两种方法时间复杂度达到了O(n),对于这道题普通的方法,时间复杂度会达到O(nk))。
方法一:根据"i&(i-1)"计算
"i&(i-1)"是将i的二进制中最右边的1变为0。也就是说,整数i的二进制中1的个数比"i&(i-1)"的二进制中1的个数多1。
【代码如下】
class Solution {
public int[] countBits(int n) {
int[] array=new int[n+1];
for(int i=1;i<=n;++i){
array[i]=array[i&(i-1)]+1;
}
return array;
}
}
方法二:根据“i/2”计算
如果正整数i是一个偶数,那么i就等于“i/2”左移一位,因此i和“i/2”的二进制中1的个数相等;如果i为奇数,那么i就等于“i/2"左移一位再加1,所以奇数i二进制中1的个数比“i/2”中1的个数多1。例如,我们可以根据3的二进制中1的个数求出6、7二进制中1的个数。
【代码如下】
class Solution {
public int[] countBits(int n) {
int[] array=new int[n+1];
for(int i=1;i<=n;++i){
if(i%2==1){
array[i]=array[i/2]+1;
}else{
array[i]=array[i/2];
}
}
return array;
}
}
3、单词长度的最大乘积
【题目描述】给定一个字符串数组,计算不包含相同字符的两个字符串的长度的最大乘积。如果所有字符串都包含相同字符,则返回0。(假设字符串中只包含英文小写字母)
本题只需要考虑26个小写字母,我们只需要26个二进制数位来代替boolean类型记录字符串中出现的字符。0表示false,1表示true。
可以用一个int类型的整数来记录某个字符串中出现的字符。如果字符中包含'a',则整数右边第一位为1,如果包含'b',则整数中右边倒数第二位为1,以此类推。这样做能更好的判断两个字符串是否包含相同的字符。如果两个字符串中包含相同的字符,则它们对应的整数的某个数位都为0,即这两个整数进行与运算的结果不为0;如果不包含相同的字符,则两个整数的与运算的结果为0.
【代码如下】
class Solution {
public int maxProduct(String[] words) {
int[] array=new int[words.length];
for(int i=0;i<words.length;++i){
for(char ch:words[i].toCharArray()){
//注意:这只能用与运算,不能用+运算,否则,如果出现重复字符,会出错
array[i]|=1<<(ch-'a');
}
}
int ret=0;
for(int i=0;i<words.length;++i){
for(int j=i+1;j<words.length;++j){
if((array[i]&array[j])==0){
int len=words[i].length()*words[j].length();
ret=ret>len?ret:len;
}
}
}
return ret;
}
}