位运算的妙用(附题目)


前言

掌握位运算,可以打开我们求解问题的思路,在某些时候,使用位运算可以方便我们解决算法问题,因此对于一些位运算的小妙招我们需要掌握。
注:本文所有例题都来源与leetcode

1、0/1==不选/选

  • 说明:对于一个二进制数,它的每一位只可能是0或者1,也就是只有两种可能,我们可以把只有两种可能这一特点转换成选/不选这两种情况,这在子集问题上有着很好的应用。
  • 题目:78.子集
    文字说明:给你一个整数数组 nums,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
    解集不能包含重复的子集。你可以按任意顺序返回解集。
  • 分析:我们都知道,一个有n个元素的集合,它的子集个数为2^n个。
    为什么是2^n呢?我们可以用排列组合的思想来思考:对于集合里的每一个数,我们都有2种做法,将其加入子集/不将其加入子集,因此对于n个元素,我们就有了 2^n个存放方法。而这2种做法刚好可以抽象成0/1,这 2^n刚好对应 0~2^n-1这些不同的数,因此我们就可以想到二进制+位运算的思想来解题了。
    比如:集合{1,3} 子集:{},{3},{1},{1,3},正好对应0(00),1(01),2(10),3(11)
  • 代码
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> subsets = new ArrayList<>();//结果集
        int bmp = (int) Math.pow(2, nums.length);
        // 从 nums.length 个 0 遍历到 nums.length 个 1
        for (int i = 0; i < bmp; i++) {
            List<Integer> subset = new ArrayList<>();
            for (int j = 0; j < nums.length; j++)
                // 将1依次左移j位,与i相与,判断该位是否为1,
                //如果为1,则相与结果不为0,否则为0
              if ((i&(1<<j)) !=0) subset.add(nums[j]);
            subsets.add(subset);//将当前子集加入结果集中
        }
        return subsets;
    }
}

2、同消异留

  • 说明:异或:对于两个数a,b和ab异或的结果c;a和b异或时判断a和b每一位上的数字是否相同,若该位数字相同则c中该位为0,否则为1。因此我们可以很容易得到结论:如果a和b相等,则a和b异或的结果c=0
  • 题目:136. 只出现一次的数字
    给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
    说明:你的算法应该具有线性时间复杂度并且不使用额外空间
  • 分析:既然除了一个数字出现了1次,其余数字都出现了两次,那么干脆让这些数字依次异或下去,异或的结果就是答案(异或过程中出现两次的数字都抵消了)。
  • 代码
class Solution {
    public int singleNumber(int[] nums) {
    int x=nums[0];
        for (int i = 1; i <nums.length ; i++) {
            x^=nums[i];//依次异或数组中每一个数字
        }
        return x;
    }
}

3、“与”判公共

  • 说明:与:对于两个数a和b和ab与运算的结果c,a和b与时判断a和b每一位上的数字是否均为1,若该位数字均为1则c该位为1,否则为0。因此我们可以很容易得到结论:如果a和b没有一位上都为1,则a和b与的结果c=0

  • 题目:318. 最大单词长度乘积
    给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。如果不存在这样的两个单词,返回 0。

  • 分析:本题的关键在于怎么判断两个单词不含有公共字母,结合上述说明,我们只需要将出现的字符用二进制数表示出来,并用与运算就能判断。那怎么用二进制数表示每个单词中出现的字符呢?注意到本题只出现小写字母,只有26位,刚好可以用int类型表示,如101表示出现字母a,c。

  • 代码

class Solution {
   public int maxProduct(String[] words) {
       int []bitwise=new int[words.length];
     for (int i = 0; i <words.length ; i++) {
        //得到每个单词出现的字符,用cur表示
        //cur的二进制数的某位为1,代表该字符在该单词中出现
        //如cur第0位为1,表示a在单词中
         int cur=0;
         for (char c:words[i].toCharArray()){
             cur|=(1<<(c-'a'));//或运算,统计字符是否出现
         }
         bitwise[i]=cur;
     }
     int ans=0;
     for (int i = 0; i <words.length-1 ; i++) {
         int len=words[i].length();
         for (int j = i; j <words.length ; j++) {
             if((bitwise[i]&bitwise[j])==0){//第i和第j个单词与操作
             //如果为0,代表它们没有重复元素,计算最大值
               ans=Math.max(ans,len*words[j].length());  
             }
         }
     }
     return ans;
 }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值