位运算题目:数字范围按位与
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:数字范围按位与
出处:201. 数字范围按位与
难度
5 级
题目描述
要求
给定两个整数 left \texttt{left} left 和 right \texttt{right} right,表示区间 [left, right] \texttt{[left, right]} [left, right],返回此区间内所有数字按位与的结果(包含 left \texttt{left} left 和 right \texttt{right} right 端点)。
示例
示例 1:
输入:
left
=
5,
right
=
7
\texttt{left = 5, right = 7}
left = 5, right = 7
输出:
4
\texttt{4}
4
示例 2:
输入:
left
=
0,
right
=
0
\texttt{left = 0, right = 0}
left = 0, right = 0
输出:
0
\texttt{0}
0
示例 3:
输入:
left
=
1,
right
=
2147483647
\texttt{left = 1, right = 2147483647}
left = 1, right = 2147483647
输出:
0
\texttt{0}
0
数据范围
- 0 ≤ left ≤ right ≤ 2 31 − 1 \texttt{0} \le \texttt{left} \le \texttt{right} \le \texttt{2}^\texttt{31} - \texttt{1} 0≤left≤right≤231−1
解法一
思路和算法
最直观的做法是遍历区间 [ left , right ] [\textit{left}, \textit{right}] [left,right] 中的所有整数计算按位与运算的结果。由于 right \textit{right} right 的最大值是 2 31 − 1 2^{31} - 1 231−1,因此遍历区间中的所有整数的时间复杂度过高,需要考虑时间复杂度更低的做法。
由于 left \textit{left} left 和 right \textit{right} right 都是非负整数,因此二进制表示的最高位即符号位是 0 0 0,只需要考虑除了最高位以外的 31 31 31 位。对于 0 ≤ i < 31 0 \le i < 31 0≤i<31,从低到高的第 i i i 位表示 2 i 2^i 2i。
如果 left \textit{left} left 和 right \textit{right} right 的二进制表示的从低到高第 i i i 位的值不同,则区间 [ left , right ] [\textit{left}, \textit{right}] [left,right] 中的所有整数按位与运算的结果的第 0 0 0 位到第 i i i 位都是 0 0 0,理由如下。
考虑两个数的二进制表示的第 0 0 0 位到第 i i i 位,区间 [ left , right ] [\textit{left}, \textit{right}] [left,right] 中一定包含一个整数 x x x,其二进制表示的第 i i i 位的值是 1 1 1,第 0 0 0 位到第 i − 1 i - 1 i−1 位的值都是 0 0 0。
-
由于 left \textit{left} left 和 right \textit{right} right 的第 i i i 位的值不同,因此按位与运算的结果的第 i i i 位的值是 0 0 0。
-
由于 x x x 参与按位与运算, x x x 的第 0 0 0 位到第 i − 1 i - 1 i−1 位的值都是 0 0 0,因此按位与运算的结果的第 0 0 0 位到第 i − 1 i - 1 i−1 位的值都是 0 0 0。
考虑 left \textit{left} left 和 right \textit{right} right 的二进制表示的连续最高位的相同部分,称为公共前缀。假设两个数的最长公共前缀为第 j j j 位到最高位,则两个数的按位与运算的结果的第 j j j 位到最高位的前缀等于两个数的最长公共前缀。
根据上述分析,计算区间 [ left , right ] [\textit{left}, \textit{right}] [left,right] 中的所有整数的按位与运算的结果等价于计算 left \textit{left} left 和 right \textit{right} right 的二进制表示的最长公共前缀,该最长公共前缀后面的位都用 0 0 0 填充,即可得到按位与运算的结果。
具体做法是,从高到低依次遍历 left \textit{left} left 和 right \textit{right} right 的二进制表示中除了最高位以外的每一位,将相等的位加到公共前缀中,遍历结束或者遇到不相等的位时结束遍历,遍历结束之后即可得到按位与运算的结果。
代码
class Solution {
public int rangeBitwiseAnd(int left, int right) {
final int BITS = 31;
int bitwiseAnd = 0;
for (int i = BITS - 1; i >= 0; i--) {
int mask = 1 << i;
int bit1 = left & mask, bit2 = right & mask;
if (bit1 == bit2) {
bitwiseAnd += bit1;
} else {
break;
}
}
return bitwiseAnd;
}
}
复杂度分析
-
时间复杂度: O ( k ) O(k) O(k),其中 k k k 是有效的二进制位数, k = 31 k = 31 k=31。需要从高到低遍历 left \textit{left} left 和 right \textit{right} right 二进制表示的最多 31 31 31 位。
-
空间复杂度: O ( 1 ) O(1) O(1)。
解法二
思路和算法
计算区间 [ left , right ] [\textit{left}, \textit{right}] [left,right] 中的所有整数的按位与运算的结果等价于计算 left \textit{left} left 和 right \textit{right} right 的二进制表示的最长公共前缀。假设两个数的最长公共前缀为第 j j j 位到最高位,则将 right \textit{right} right 的第 0 0 0 位到第 j − 1 j - 1 j−1 位的值都设成 0 0 0 之后, right \textit{right} right 的值即为最长公共前缀后面用 0 0 0 填充之后的值,且此时 right ≤ left \textit{right} \le \textit{left} right≤left。
为了将 right \textit{right} right 的第 0 0 0 位到第 j − 1 j - 1 j−1 位的值都设成 0 0 0,需要将其中的值为 1 1 1 的位都变成 0 0 0。可以使用 Brian Kernighan 算法实现,其原理是:对于任意整数 n n n, n & ( n − 1 ) n ~\&~ (n - 1) n & (n−1) 的结果是将 n n n 的二进制表示的最后一个 1 1 1 变成 0 0 0 之后的整数。
当 left < right \textit{left} < \textit{right} left<right 时, right \textit{right} right 的二进制表示的最后一个 1 1 1 不在最长公共前缀中,因此将 right \textit{right} right 的二进制表示的最后一个 1 1 1 变成 0 0 0。重复该操作,直到 left ≥ right \textit{left} \ge \textit{right} left≥right 时, right \textit{right} right 的二进制表示的每一个 1 1 1 都在最长公共前缀中,此时 right \textit{right} right 即为最终结果。
代码
class Solution {
public int rangeBitwiseAnd(int left, int right) {
while (left < right) {
right = right & (right - 1);
}
return right;
}
}
复杂度分析
-
时间复杂度: O ( log right ) O(\log \textit{right}) O(logright),其中 right \textit{right} right 是给定的区间上界。Brian Kernighan 算法的时间复杂度是 O ( log right ) O(\log \textit{right}) O(logright)。
-
空间复杂度: O ( 1 ) O(1) O(1)。