算法学习-位运算以及进制表示有关的问题,让脑袋像机器一样思考得到光荣进化(持续更新中)


作为计算机专业的学生,对于各种二进制表示数的方法需要具备一定的敏感性,但是每次碰到相关的题目或者算法题,却总是不能第一时间反应出来,因此本专题对相关题目进行整理汇总,让自己的脑子具备“二进制思维”。

相关知识

1. 关于补码的一些认识

拿有符号8位的二进制表示的数值范围:-27…27-1

0111 1111是最大的正数 127
1000 0000是最小的负数(绝对值最大的负数)-128
在这里插入图片描述
一个需要确立的感性认知是:当我们加一个数时,顺时针旋转,减去一个数时,逆时针旋转。当数字本身很大,加上一个数两极反转,反倒变成最小;当数字本身很小,减去一个数两极反转,反倒变成最大。

我们经常会碰到位运算求数值的问题,该计算方法对补码负数同时有效,对于8位有符号数来说:

byte ans=0;
for (int i = 0; i < 8; i++) {
   ans += (1 << i);
}

1000 0011(1<<7)+(1<<1)+(1<<0)=-128+2+1=-125 算出来也是负数,符合逻辑。原因是8位的最大数为(1<<7)-1,所以1<<7实际上已经溢出为负数了。

2. Java右移需要考虑有无符号位移

">>>"无符号右移
操作规则:无论正负数,前面补零。
">>"右移
操作规则:正数前面补零,负数前面补1
"<<"左移
操作规则:无论正负数,后面补零。

3. 移位运算时需要考虑到的运算符优先级

单算移比位逻三赋
在这里插入图片描述
在这里插入图片描述

相关题目

191.位1的个数

参考负雪明烛大神的题解,可以采用移位32次的方法,但也可以采用一种技巧性的位运算,n&(n-1)可以消除二进制末尾的1,那么只需要记录n消除几次1最终变为0,即为答案。

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int res=0;
        //题目给的是32位的数,考虑补码首位符号位的情况
        for(int i=0;i<32;i++){
            int temp=n>>>i&1;
            res+=temp==1?1:0;
        }
        return res;
    }
}
public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int res=0;
        while(n!=0){
            n=n&n-1;
            res++;
        }
        return res;
    }
}
268.丢失的数字

本题可以采用多种方法进行求解,参考宫水三叶的题解,在数组内只丢失了唯一确定的一个数字。
异或法:任何数同0异或是其本身,任何数和其本身异或是0,充分考察位运算。

class Solution {
    public int missingNumber(int[] nums) {
        int ans=0;
        int len=nums.length;
        //包括丢失的数以后的所有数字
        for(int i=0;i<=len;i++){
            ans^=i;
        }
        //让出现的数字异或为0,最后剩下丢失的数字
        for(int i:nums){
            ans^=i;
        }
        return ans;
    }
}
136.只出现一次的数字

同样是异或操作,把出现偶数次的数字消掉,只剩下了出现一次的数字。

class Solution {
    public int singleNumber(int[] nums) {
        int res=0;
        for(int i:nums){
            res^=i;
        }
        return res;
    }
}
137.只出现一次的数字II

位数统计,将所有数字在int类型上的每一位上出现1的次数统计起来,消除出现三次的元素的方法是,已经累加的位数一一对3取模,最后只剩下取模无法消去的只出现一次的元素。

class Solution {
    public int singleNumber(int[] nums) {
        int[]bit=new int[32];
        for(int i=0;i<32;i++){
            for(int n:nums){
                if((n>>i&1)==1){
                    bit[i]++;
                }
            }
        }
        int ans=0;
        for(int i=0;i<32;i++){
            if(bit[i]%3!=0){
                ans|=1<<i;
            }
        }
        return ans;
    }
}
458.可怜的小猪

参考宫水三叶大神的题解自定义进制表示,并且用进制位数对应实验对象数目,二进制表示能够在相互制约中表示更多信息,提高了笔者对于进制的理解。

class Solution {
    //宫水三叶yyds,k进制来表示轮数
    public int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
        int res=(int)Math.ceil(Math.log(buckets)/Math.log(minutesToTest/minutesToDie+1));
        return res;
    }
}
66.加一

从末尾开始+1,+1直到没法进位(进位则%10以后为0)为止,如果全部数位都进位了,需要新构建一个1XXXXX的数组

class Solution {
    //从末尾开始+1,+1直到没法进位为止,如果全部数位都进位了,需要新构建一个1XXXXX的数组
    public int[] plusOne(int[] digits) {
        int len=digits.length;
        for(int i=len-1;i>=0;i--){
            digits[i]++;
            digits[i]%=10;
            //如果不为0,说明该位没进位,如果为0,则一直往前进位
            if(digits[i]!=0) return digits;
        }

        //如果全部都进位了
        int[]res=new int[len+1];
        res[0]=1;
        return res;
    }
}
2.二进制加法

有两种做法,其一是参考lilyunoke大神的加法模板,题解如下:

class Solution {
    public String addBinary(String a, String b) {
        int i=a.length()-1;
        int j=b.length()-1;
        int carry=0;
        StringBuilder sb=new StringBuilder();
        while(i>=0||j>=0){
            int digitA=i>=0?a.charAt(i)-'0':0;
            int digitB=j>=0?b.charAt(j)-'0':0;
            int sum=digitA+digitB+carry;
            carry=sum/2;
            int digit=sum%2;
            sb.append(digit);
            i--;
            j--;
        }
        if(carry!=0) sb.append(carry);
        return sb.reverse().toString();
    }
}

其中可以总结出来的加法模板是,这个模板可以解决很多逐位相加运算的问题,关注A当前位、B当前位、进位三要素:

while ( A 没完 || B 没完){
	取到A 的当前位(A完了需要补位0)
    取到B 的当前位(B完了需要补位0)

    和 = A 的当前位 + B 的当前位 + 进位carry

    当前位 =% 2(10);
    进位 =/ 2(10);
    
    加入结果集
    
    A左移调整
    B左移调整
}
判断进位是否为0,不为0额外加上
将结果集反转

或者参考负雪明烛大佬的题解,不同的是循环条件,需要注意的是while循环结束条件,注意需要遍历完两个「加数」,以及进位不为0。

class Solution {
    public String addBinary(String a, String b) {
        int i=a.length()-1;
        int j=b.length()-1;
        int carry=0;
        StringBuilder sb=new StringBuilder();
        while(i>=0||j>=0||carry!=0){
            int digitA=i>=0?a.charAt(i)-'0':0;
            int digitB=j>=0?b.charAt(j)-'0':0;
            int sum=digitA+digitB+carry;
            carry=sum>=2?1:0;
            int digit=sum>=2?sum-2:sum;
            sb.append(digit);
            i--;
            j--;
        }
        return sb.reverse().toString();
    }
}
415.字符串相加

方法同上,只不过改成了十进制加法

class Solution {
    public String addStrings(String num1, String num2) {
       int i=num1.length()-1;
       int j=num2.length()-1;
       int carry=0;
       StringBuilder sb=new StringBuilder();
       while(i>=0||j>=0){
           int digitA=i>=0?num1.charAt(i)-'0':0;
           int digitB=j>=0?num2.charAt(j)-'0':0;
           int sum=digitA+digitB+carry;
           int digit=sum%10;
           carry=sum/10;
           sb.append(digit);
           i--;
           j--;
       } 
       if(carry!=0) sb.append(carry);
       return sb.reverse().toString();
    }
}
989.数组形式的整数加法
class Solution {
    public List<Integer> addToArrayForm(int[] num, int k) {
        int i=num.length-1;
        int carry=0;
        LinkedList<Integer> ans=new LinkedList<>();
        while(i>=0||k!=0){
            int digitA=i>=0?num[i]:0;
            int digitB=k!=0?k%10:0;
            int sum=digitA+digitB+carry;

            int digit=sum%10;
            carry=sum/10;

			//直接加到首位
            ans.add(0,digit);

            i--;
            k/=10;
        }
        if(carry!=0) ans.add(0,carry);
        return ans;
    }
}
5.单词长度的最大乘积

如果遍历每一对单词,然后每次判断是否有重复元素,复杂度为O(N^2*L),约为O(10^9),TLE。因此尝试是否能将判断公共字母的时间复杂度降为O(1),所以我们需要进行一步预处理。针对每个单词words[i],采用位掩码记录该单词中出现过的字母,即如果那个字母出现,对应位置1, masks[i] |= 1 << (word.charAt(j) - 'a');,时间复杂度O(N*L),总时间复杂度O(N*L+N^2),约为O(10^6)

class Solution {
    public int maxProduct(String[] words) {
        int len=words.length;
        int[]states=new int[len];
        for(int i=0;i<len;i++){
            String w=words[i];
            int wlen=w.length();
            for(int j=0;j<wlen;j++){
                //通过掩码数字来记录每个字符串出现的字母
                states[i]|=1<<w.charAt(j)-'a';                
            }
        }
        int res=0;
        for(int i=0;i<len;i++){
            for(int j=i+1;j<len;j++){
                if((states[i]&states[j])==0){
                    res=Math.max(res,words[i].length()*words[j].length());
                }
            }
        }
        return res;
    }
}
7.整数反转

很经典的整数反转题目,return结果很妙。

class Solution {
    public int reverse(int x) {
        long res=0;
        while(x!=0){
            res=res*10+x%10;
            x/=10;
        }
        return (int)res==res?(int)res:0;
    }
}
9.回文数

用反转整数的方法,计算出反转的整数,最后判断两个整数是否相等。

class Solution {
    public boolean isPalindrome(int x) {
    	//负数一定不是回文数
        if(x<0) return false;
        //计算反转整数
        long res=0;
        int quo=x;
        while(quo!=0){
            res=res*10+quo%10;
            quo/=10;
        }
        //判断溢出
        if((int)res!=res) return false;
        return (int)res==x;
    }
}
2401.最长优雅子数组

滑动窗口+位运算。用一个数字窗口window维护整型32位上出现的数字,每次移动right只需要判断当前数字与窗口中的&以后,是否为0,如果为0则代表所有数字都散落在窗口的不同位置,如果不为0则需要收缩左边界。可以保证的是,如果当前的数字与窗口&只为0才加入,那么窗口中所有的数字互相&都是0.

class Solution {
    public int longestNiceSubarray(int[] nums) {
        int res=0;
        int len=nums.length;
        int window=0;
        int left=0;
        int right=0;
        while(right<len){
            int temp=window & nums[right];
            while(temp!=0){
                window ^= nums[left];
                temp = window & nums[right];
                left++;
            }
            window |= nums[right];
            res=Math.max(res,right-left+1);
            right++;
        }
        return res;
    }
}
805.数组的均值分割

二进制枚举+折半查找。参考ylb大神的题解,其中通过二进制来表示对应的位被枚举出来值得学习。

class Solution {
    public boolean splitArraySameAverage(int[] nums) {
        int n = nums.length;
        if (n == 1) {
            return false;
        }
        int s = Arrays.stream(nums).sum();
        for (int i = 0; i < n; ++i) {
            nums[i] = nums[i] * n - s;
        }
        int m = n >> 1;
        Set<Integer> vis = new HashSet<>();
        // i代表前面几位所有可能的二进制组合情况,j对每种情况的所有位进行检查,如果为1就将它加入和
        for (int i = 1; i < 1 << m; ++i) {
            int t = 0;
            for (int j = 0; j < m; ++j) {
                if (((i >> j) & 1) == 1) {
                    t += nums[j];
                }
            }
            if (t == 0) {
                return true;
            }
            vis.add(t);
        }
        for (int i = 1; i < 1 << (n - m); ++i) {
            int t = 0;
            for (int j = 0; j < (n - m); ++j) {
                if (((i >> j) & 1) == 1) {
                    t += nums[m + j];
                }
            }
            if (t == 0 || (i != (1 << (n - m)) - 1) && vis.contains(-t)) {
                return true;
            }
        }
        return false;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网民工蒋大钊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值