从零学算法771

给你一个字符串 jewels 代表石头中宝石的类型,另有一个字符串 stones 代表你拥有的石头。 stones 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。
字母区分大小写,因此 “a” 和 “A” 是不同类型的石头。
示例 1:
输入:jewels = “aA”, stones = “aAAbbbb”
输出:3
示例 2:
输入:jewels = “z”, stones = “ZZ”
输出:0

  • 暴力解法就是双重循环,遍历 stones 时每个字符都去 jewels 找是不是宝石。很容易想到的就是既然每次都要从 jewels 找,不如直接用一个 set 存了所有宝石
  •   public int numJewelsInStones(String jewels, String stones) {
          Set<Character> set = new HashSet<>();
          for(char c:jewels.toCharArray()){
              set.add(c);
          }
          int count = 0;
          for(char c:stones.toCharArray()){
              if(set.contains(c)){
                  count++;
              }
          }
          return count;
      }
    
  • 虽然这很简单,但是如果理解透彻位运算与集合,就能更好地解决这一题。由于大写字母在 ASCII 表中二进制的低 6 位为 000001 ~ 011010,小写字母低 6 位为 100001 ~ 111010(即 58),所以我们只需要用一个 64 位整数就能表示包含所有字母的集合。或者以我的理解,这里最大的为 z 为 122,最小的为 A 为 65,以他们的范围,即宽度为 58 的范围,64 位整数足以表示

集合可以用二进制表示,二进制从低到高第 i 位为 1 表示 i 在集合中,为 0 表示 i 不在集合中。比如 {0,1,3} ,也就是说第 0、1、3 位为 1,第 2 位为 0 ,即 1011

  • 了解了最基本的定义以后我们进入正题,如果我们能把每个宝石对应的字母转为一个单元素的集合,然后取并集为 m,最后遍历石头时同样转为单元素集合 n,然后看 n 是否被包含于 m 即可。所以这里需要知道的只有三点,第一点如何表示单元素集合,第二点集合如何取并集,第三点如何判断集合的包含关系。
  • 单元素集合: 先看几个数观察一下,比如 i 为 1,2,3 时对应的集合为 {0010},{0100},{1000}(为了方便只举了数字较小的例子,用四位就能表示);你会发现他们分别对应 2 的 i 次,也就是说可以分别表示为 1<<1,1<<2,1<<3 即 i 表示为集合为 {i},对应位运算为 1<<i(<<i 就相当于乘以 2 的 i 次
  • 并集: 我们知道比如 {0,1,2} U {0,2,3} = {0,1,2,3}0111 ? 1101 = 1111 符合条件的只有或运算即 0111 | 1101 = 1111,所以并集操作对应位运算为 A U B = A | B
  • 集合包含某个元素: i 是集合 S 的元素,则可以得到 (s>>i)&1=1,也不难理解,右移 i 位相当于除以 2 的 i 次,等于把一个二进制中所有的 1 往右移动 i 位,移动后的补 0。比如 1011 右移两位,先移一位得到 0101,再移一位得到 0010。那么比如元素 2 和 集合 {0,2,3} (对应二进制为 1101), s>>i 即为 1101 >> 2,你会发现其实就是把第 2 位上的 1 移动到了第 0 位。也就是说只要这个集合包含了 2,那么其余位我不管,你低四位肯定为 x1xx,不包含则必定为 x0xx,那么我把第 2 位移动到第一位后一定能得到 xxx1 或者 xxx0,此时只要 & 上 1 也就是 & 上 0001 就能判断这个集合是否包含 2。反过来推一遍,一个集合 S 想知道是否包含元素 i,就需要知道这个集合对应二进制的第 i 位是否为 1,而想取第 i 位正好只需要右移 i 位就能把第 i 位移到第 0 位,最后 & 1 就能知道它是 0 还是 1,即 S>>i&1==1?包含:不包含
  • 接下来就简单了:
  •   public int numJewelsInStones(String jewels, String stones) {
          long mask = 0;
          for(char c:jewels.toCharArray()){
          // c & 63 就是转为 64 位二进制数
          // 或者你也可以写成 c-'A',无非十进制和二进制的区别
              mask |= 1L<<(c&63);
          }
          int count = 0;
          for(char c:stones.toCharArray()){
              count += mask>>(c&63)&1;
          }
          return count;
      }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值