题目链接
题目背景
我们需要解决的问题是:给定一个整数数组 nums 和两个整数 low 和 high,统计满足条件的“漂亮数对” (i, j) 的数量,其中:
0 <= i < j < nums.length
low <= (nums[i] XOR nums[j]) <= high
简单来说,我们要找所有满足条件的数对 (i, j),使得 nums[i] 和 nums[j] 的异或值在 [low, high] 范围内.
什么是异或(XOR)?
异或是一种位运算,符号是 ^.它的规则是:如果两个比特位相同,结果是 0;如果不同,结果是 1.比如:
5 ^ 3:
5 的二进制是 101
3 的二进制是 011
按位异或:101 ^ 011 = 110,即十进制的 6
异或的一个重要性质是:如果 x ^ y = t,那么 y = x ^ t。这在题目中会很有用
暴力解法:O(n²) 的复杂度
最直观的思路是两重循环,枚举所有可能的数对 (i, j),计算 nums[i] ^ nums[j],然后判断是否在 [low, high] 范围内:
int countPairs(vector<int>& nums, int low, int high) {int ans = 0;for (int i = 0; i < nums.size(); i++) {for (int j = i + 1; j < nums.size(); j++) {int xorVal = nums[i] ^ nums[j];if (xorVal >= low && xorVal <= high) {ans++;}}}return ans;
}
但题目中 nums 的长度可以达到 2 * 10^4,这种方法的时间复杂度是 O(n²),会超时.我们需要更高效的解法
高效解法:利用位运算和前缀统计
题目中 nums[i] 和 low、high 的范围都在 2 * 10^4 内,意味着它们的二进制表示最多有 15 位(因为 2^14 = 16384 < 2 * 10^4 < 2^15 = 32768)。我们可以利用位运算,从高位到低位逐步统计
核心思想:从高位到低位统计
我们将问题转化为:统计异或值小于某个数 t 的数对数量。最终答案是:
异或值小于 high + 1 的数对数量 减去 异或值小于 low 的数对数量。
即:countPairs(nums, low, high) = countLessThan(high + 1) - countLessThan(low)
如何统计异或值小于 t 的数对数量?
我们从高位到低位(从第 14 位到第 0 位)逐步统计:
用哈希表统计前缀:用一个哈希表 cnt 记录当前前缀的出现次.初始时,cnt[x] 表示 nums 中值为 x 的数的个数
逐位处理:对于当前位(第 k 位),我们只关心 nums[i] 的前 k 位,忽略低位.
计算贡献:如果 t 的第 k 位是 1,我们需要统计满足条件的数对;如果 t 的第 k 位是 0,则跳过.
更新前缀:将 cnt 中的每个键右移一位(即忽略最低位),进入下一轮统计。
举例说明
以 [0, 10100] 为例(10100 是二进制,表示十进制的 20),我们统计异或值小于 10100 的数对数量:
第 4 位(最高位):t = 10100,第 4 位是 1
假设 nums 中有 2 个数的前 4 位是 10000,3 个数的前 4 位是 00000
我们需要统计异或值的前 4 位是 10000 的数对数量
对于 x = 10000,x ^ t = 10000 ^ 10000 = 00000,cnt[00000] = 3,所以贡献是 2 * 3 = 6
类似地计算其他前缀。
右移:将 cnt 中的键右移一位,进入第 3 位统计
为什么可以这样统计?
当我们只看前 k 位时,低位可以是任意值(0 或 1),这相当于统计了所有可能的低位组合
比如,统计前 3 位时,t = 100 实际上包含了 t = 10000, 10001, 10010, 10011 的所有情况
代码实现
class Solution {
public:int countPairs(vector<int>& nums, int low, int high) {int ans = 0;unordered_map<int, int> cnt;// 统计每个数的出现次数for (int x : nums) ++cnt[x];// 从高位到低位统计for (++high; high; high >>= 1, low >>= 1) {unordered_map<int, int> nxt;for (auto& [x, c] : cnt) {// 如果 high 的当前位是 1,计算贡献if (high & 1 && cnt.count(x ^ (high - 1))) {ans += c * cnt[x ^ (high - 1)];}// 如果 low 的当前位是 1,减去贡献if (low & 1 && cnt.count(x ^ (low - 1))) {ans -= c * cnt[x ^ (low - 1)];}// 右移一位,更新前缀nxt[x >> 1] += c;}cnt = move(nxt);}// 每对被统计了两次((i, j) 和 (j, i)),所以除以 2return ans / 2;}
};
复杂度分析
时间复杂度:O(n + n * log U),其中 U = max(nums)。log U 是因为我们需要处理 U 的每一位(最多 15 位)
思路来源:灵神题解 有点赶了就这样吧