位运算技巧

计算类知识总结

常用计算

  1. 将 n 二进制表示的最低位 1 移除
    n & (n-1)
  2. 获取 n 二进制表示的最低位的 1
    n & (-n)
    其中 -n = ~n + 1
  3. 获取 n 二进制的第 i 位
    (n >> i) & 1
  4. 将 n 二进制的第 i 位设为1
    (n | (1 << i))
  5. 取模
    当 n 为2的幂的时候,可以用位运算符 & 来取代 %
    %8 和 &7 是等价的。同理 %16 等价于 &15,%32 等价于 &31 …

Gosper’s Hack (生成 n元集合所有 k 元子集)

例如从 7 个元素中选 4个元素。

某个集合为 0101110 ,下个集合应该是 0110011。
怎么快速进行转换呢?

记 x 为当前集合,则有:

public int next(int x) {
    int lb = x & (-x);
    int left = x + lb;
    int right = (x ^ (x + lb)) / lb >> 2;
    return left + right;    // 左右两边拼起来,用 left | right 也可以
}

相关的题目可见:【算法总结】——子集型回溯

判断类知识总结

判断一个数是否可以分解成 k 个 2的任意幂次

9 = 1001 9 = 1001 9=1001 为例,判断其是否可以分解成 3 个 2的任意幂次

只要 k ∈ [ x . b i t _ c o u n t ( ) , x ] k ∈ [x.bit\_count(), x] k[x.bit_count(),x],就可以完成分解。

一些奇技淫巧

字符大小写相互转换

char a = 'a';
System.out.println((char) (a ^ 32));	// 输出 A

原理解释:以 ‘a’ 和 ‘A’ 为例,
65 的补码为 01000001
~90 01011010
97 的补码为 01100001
~122 01111010
32 的补码为 00100000

所以可以看出 大小写之间的 差异就在二进制表示的 32 那一位上,因此 异或 1 取反,就正好大小写转换了。

英文字母表示成 0 ~ 64 之间的数字

‘A’ ~ ‘Z’ 是 65 ~ 90 (1000001~1011010)
‘a’ ~ ‘z’ 是 97 ~ 112(1100001~1110000)

63 的 二进制表示是:111111

将上述范围变成了 1 ~ 26 和 33 ~ 58。

因此可以用 long 类型的 mask 表示英文字母集合。

相关应用可见:【LeetCode每日一题合集】2023.7.24-2023.7.30

表示1~k的全1集合

(2<<k)-2

同理的,后面可以使用(4<<k)-4表示从2~(2-1+k)的全1集合

例题

LeetCode 5993. 将找到的值乘以 2

https://leetcode-cn.com/problems/keep-multiplying-found-values-by-two/

class Solution {
    public int findFinalValue(int[] nums, int original) {
        int mask = 0;
        for (int num: nums) {
            int k = num / original;
            if (num % original == 0 && (k & (k - 1)) == 0) {    // 倍数是 2 的幂次
                mask |= k;
            }
        }
        mask = ~mask;   // 取反后,找最低位的 1(lowbit = mask & -mask)
        return original * (mask & -mask);
    }
}

二进制数mask记录nums 中含有哪些 original 的 2 幂次倍数。
遍历完 nums 后,我们可以模拟题目的过程,即从mask 的最低位开始,找连续的 2 的幂次倍数,即连续的 1 的个数。

1177. 构建回文串检测

https://leetcode.cn/problems/can-make-palindrome-from-substring/

class Solution {
    public List<Boolean> canMakePaliQueries(String s, int[][] queries) {
        int n = s.length();
        int[] cnt = new int[n + 1];
        for (int i = 0; i < n; ++i) {
            cnt[i + 1] = cnt[i] ^ (1 << (s.charAt(i) - 'a'));	// 前缀数组
        }
        List<Boolean> res = new ArrayList();
        for (int i = 0; i < queries.length; ++i) {
            int l = queries[i][0], r = queries[i][1], k = queries[i][2];
            int bits = 0, x = cnt[r + 1] ^ cnt[l];
            // 计算1的个数
            while (x != 0) {
                x &= x - 1;
                bits++;
            }
            res.add(bits <= 2 * k + 1);
        }
        return res;
    }
}

参考资料

二进制补码计算器

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wei *

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

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

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

打赏作者

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

抵扣说明:

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

余额充值