持续创作,加速成长!这是我参与「掘金日新计划 · 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)交替位二进制数(易)
题干分析
给定一个正整数,检查它的二进制表示是否总是 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 = 5
bin(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;
}
}
(2)数字 1 的个数(难)
题干分析
给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。
示例 1:
输入:n = 13
输出:6
示例 2:
输入:n = 0
输出:0
提示:0 <= n <= 109
思路解法
思路:
- 暴力法:每一个数字计算一遍
o(n * log10(n))
- 按位求数量
- 找规律,递归法
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
# 计算: