LeetCode 1178 猜字谜 代码越短 越难搞 详解 sub = (sub-1)&k

LeetCode 1178 猜字谜 代码越短 越难搞 详解 sub = (sub-1)&k

猜个字谜,整个元宵节都不在状态了,果然代码越短越难搞。一个简单的&操作,困在里面整整一天,一杯水,一包烟,一个与k算一天。不过还是走出来了!这里先行致歉,水平有限,折磨了一天也不能将子集的生成过程讲解出来,不是不懂,而是讲不出。这里抛砖引玉,希望来位大哥指路

二进制数的子集是什么

1 0 10 10_{10} 1010为例, 1 0 10 = 101 0 2 10_{10}=1010_2 1010=10102, 二进制表示中,我们关注bit位,它的子集满足

  • 为1的bit位可以取0,也可以取1。可以改变 yes
  • 为0的bit为不能改变。不能改变 no

也就是 1 y e s 0 n o 1 y e s 0 n o 1_{yes}0_{no}1_{yes}0_{no} 1yes0no1yes0no,共有 2 c o u n t 2^{count} 2count个子集,其中 c o u n t 为 b i t = 1 count为 bit=1 countbit=1的位数,此处为2。

1 y e s 1_{yes} 1yes 0 n o 0_{no} 0no 1 y e s 1_{yes} 1yes 0 n o 0_{no} 0no
1010
1000
0010
0000

热身操作 sub = (sub-1)&sub

注意,这里是 (sub-1)&sub,具体有什么区别马上开讲。

num=(num-1)&num

public static void enumBit(int num){
    System.out.println(Integer.toBinaryString(num));
    do{
        num = (num-1)#
        System.out.println(Integer.toBinaryString(num));
    }while(num!=0);
}
public static void main(String[] args) {
    int num = 12;
    Problem1178.enumBit(num);
}
1100
1000
0

很容易看出,num=(num-1)&num 将 num 最右侧的1置0(二进制)。它是怎么实现的呢?我们先从 num-1 讲起。

num-1

num=24为例 [ 24 ] 10 − 1 = [ 10100 ] 2 − 1 = [ 10011 ] 2 [24]_{10} - 1 = [10100]_2 - 1 = [10011]_2 [24]101=[10100]21=[10011]2,右侧为0部分会向高位借1,而右侧第一个1因为借给了低位变成0,而它右侧的0会变成1。

image-20210226174736404

再次强调:(num-1)将num右侧第一个1置0,而其右侧的0变成1。我们再来看看 (num-1)&num是什么操作。

(num-1)&num

image-20210226175340802

一个简单的&操作,没有太多难处。建议不看配图,自己想想就好了

重要 重要 重要 当前操作位

MarkDown,为了讲解模拟sub=(sub-1)&k的思路,费劲心思,百般折磨,回过头来添加的这个概念。在sub的二进制表示中,我们从右向左数,第几个bit为1就是第几个操作位

image-20210227130521151

对应的do{ sub=(sub-1)&sub} while(sub!=0);会执行4次,每次使其对应的操作位上的1置0。但我们代码中没有当前操作位这个变量,只是为了方便讲解

模拟 sub = (sub-1)&k 操作

概述思路

关于思路的讲解,我实在太难了,我不知道怎么讲,才能更简单明了。下面只是为了概述思路,*请大家忽略所有细节,不要疑惑 complete为什么等于1010000(二进制)?当前操作位为什么在这个位置?*怎样将sub_的操作位置0?

s u b = 8 5 10 = [ 1010101 ] 2 sub=85_{10}=[1010101]_2 sub=8510=[1010101]2,当前操作位取第三位为例

image-20210227140718969

我们第4步求出来的结果是什么? 我描述不到位,题友们写出程序后,可以参考程序运行,体会结果这里大概思想已经出来了 枚举+ 递归,我们枚举每一个操作位,递归的求解子集。

枚举过程

从右向左枚举每一个操作位,由于会将操作位上的1置0,所以不会产生重复解。而为什么刚好是解,描述不出来。菜是原罪

模拟 sub=(sub-1)&k的代码

public static List<Integer> enumBit(final int sub){
    // 递归出口  0 只有一个子集
    if(sub==0){
        return Arrays.asList(0);
    }
    // 递归出口  1 有两个子集 1,0
    if(sub==1){
        return Arrays.asList(1,0);
    }
    // 变量初始化
    int preComplete = sub;
    int complete = sub;
    int remain;
    List<Integer> res = new ArrayList<>();
    List<Integer> subRes=new ArrayList<>();
    res.add(sub);
    // 枚举每一个操作位
    do {
        // 进入下一个操作位,将complete的当前操作位从1置0, 从而不会产生重复的结果
        complete = (complete-1)&complete;
        // 求remain
        remain = sub - preComplete;
        // 递归 求remain子集合
        subRes = enumBit(remain);
        // 为子集合每个元素添加偏移,并添加到结果集
        for (Integer subRe : subRes) {
            res.add(subRe+complete);
        }
        // 维护变量
        preComplete = complete;
    } while (complete != 0);
    return res;

}

十分抱歉,肝不动了。本想讲解的更详细的,奈何水平不够,已经懂的东西,想要讲解清楚,又上了一个难度啊。我们的模拟操作和&k执行结果是一样的,题友们可自行品味。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值