【算法】位运算总结

291 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

前言

位运算:

符号说明运算规则
&两个位均为1,则为1
两个位都为0, 则为0
^异或两个位相同为0, 相异则为1
~取反0变1, 1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,对于无符号数,高位补0; 有符号数,有的补符号位

小技巧:

  • 异或运算: x ^ 0 = x
  • 与运算:x & 0 = 0

快速求数字二进制表示中 1 的个数:

// n & (n - 1):消除最低位的 1
// Time:O(k) 1的个数,Space:O(1)
while (n != 0) {  
    ++cnt;
    n &= (n - 1);
}



题目

(1)交替位二进制数(易)

LeetCode 693. 交替位二进制数

题干分析

给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。

示例 1:
​
输入:n = 5
输出:true
解释:5 的二进制表示是:101
示例 2:
​
输入:n = 7
输出:false
解释:7 的二进制表示是:111.
示例 3:
​
输入:n = 11
输出:false
解释:11 的二进制表示是:1011.

思路解法

位运算,骚操作:n ^ (n >> 1)

  • ^ 异或运算:
  • n >> 1 运算:位右移 1 位,即除 2

假设 n 为交替数,01 交替的二进制数字,那么经过 n ^ (n >> 1) 运算后,得到全为 1 的二进制数。

举个栗子:

# 第一步
# n = 5bin(5)           :  101
bin(5 >> 1)      :   10
bin(5 ^ (5 >> 1)):  111
​
​
# 第二步
# 那么利用这个性质
bin(7)           :  111
bin(7 + 1)       : 1000
bin(7 & 7 + 1)   : 0000# 经过这两次运算,最后判断结果是否为 0,则可以判断出是否为交替数

AC 代码如下:

class Solution {
    public boolean hasAlternatingBits(int n) {
        n ^= n >> 1;
        return (n & n + 1) == 0;
    }
}

LeetCode 693. 交替位二进制数


(2)数字 1 的个数(难)

LeetCode 233. 数字 1 的个数

题干分析

给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。

示例 1:

输入:n = 13
输出:6

示例 2:

输入:n = 0
输出:0

提示:0 <= n <= 109

思路解法

思路:

  1. 暴力法:每一个数字计算一遍 o(n * log10(n))
  2. 按位求数量
  3. 找规律,递归法
class Solution {
    
    // 方式一:暴力法
    // Time: O(n * log10(n)), Space: O(1), Faster: Time Limit Exceeded
    public int countDigitOne(int n) {
        if (n <= 0) return 0;
​
        int result = 0;
        for (int i = 1; i <= n; ++i) {
            int cnt = countDigit(i);
            result += cnt;
        }
        return result;
    }
​
    private int countDigit(int n) {
        int cnt = 0;
        while (n != 0) {
            if (n % 10 == 1) ++cnt;
            n /= 10;
        }
        return cnt;
    }
    
    // 方法二:
    // Time: O(log10(n)), Space: O(1), Faster: 100.00%
    public int countDigitOneMath(int n) {
        if (n < 1) return 0;
        long count = 0, factor = 1;
        while (n / factor != 0) {
            long digit = (n / factor) % 10;
            long high = n / (10 * factor);
            if (digit == 0) {
                count += high * factor;
            } else if (digit == 1) {
                count += high * factor;
                count += (n % factor) + 1;
            } else {
                count += (high + 1) * factor;
            }
            factor = factor * 10;
        }
        return (int) count;
    }
    
    // 方法三:找规律
    public int countDigitOne3(int n) {
        return f(n);
    }
​
    private int f(int n) {
        // 上一级递归 n = 20、10之类的整十整百之类的情况;以及n=0的情况
        if (n == 0) return 0;
        // n < 10 即为个位,这样子只有一个1
        if (n < 10) return 1;
​
        String s = String.valueOf(n);
        //长度:按例子来说是4位
        int length = s.length();
​
        // 这个base是解题速度100%的关键,本例中的是999中1的个数:300
        // 99的话就是20 ; 9的话就是1 ;9999就是4000 这里大家应该发现规律了吧。
        int base = (length - 1) * (int) Math.pow(10, length - 2);
​
        // high 就是最高位的数字
        int high = s.charAt(0) - '0';
        // cur 就是当前所数量级,即1000
        int cur = (int) Math.pow(10, length - 1);
        if (high == 1) {
​
            // 最高位为 1,1 + n - cur就是1000~1234中由千位数提供的1的个数,
            // 剩下的f函数就是求1000~1234中由234产生的1的个数
            return base + 1 + n - cur + f(n - high * cur);
        } else {
            // 有多少个 1000 = f(999) + 1
            return base * high + cur + f(n - high * cur);
        }
    }
}

觉的方法三比较好理解:

  • 999 中有 1 的个数:300个
  • 99 中有 1 的个数:20个
  • 9 中有 1 的个数:1个
# 举个栗子: 1000
# 计算: