题目大意:
统计异或值在范围内的数对有多少
给你一个整数数组 nums (下标 从 0 开始 计数)以及两个整数:low 和 high ,请返回 漂亮数对 的数目。
漂亮数对 是一个形如 (i, j) 的数对,其中 0 <= i < j < nums.length 且 low <= (nums[i] XOR nums[j]) <= high 。
- 输入输出
输入:nums = [1,4,2,7], low = 2, high = 6
输出:6
解释:所有漂亮数对 (i, j) 列出如下:
- (0, 1): nums[0] XOR nums[1] = 5
- (0, 2): nums[0] XOR nums[2] = 3
- (0, 3): nums[0] XOR nums[3] = 6
- (1, 2): nums[1] XOR nums[2] = 6
- (1, 3): nums[1] XOR nums[3] = 3
- (2, 3): nums[2] XOR nums[3] = 5
- 数据范围
1 <= nums.length <= 2 * 104
1 <= nums[i] <= 2 * 104
1 <= low <= high <= 2 * 104
思路分析
闲扯
这道题给人的第一感觉就是直接暴力枚举,一看数据范围就知道还是刷题不够,这个2e4范围明显不能暴力枚举,赛后听群里大佬说用Trie树,虽然知道Trie树的基本用法,但还是不会bushi,看了题解才发现只会用Trie树求最大异或对是远远不够的
思路
- 我们使用Trie树来存储 (1-i-1) 的所有数,第i个数为x,这里采用边插入边查询,即每次插入x之前查询 (1-i-1) 中满足与x异或在范围内的,所以需要开辟的空间为:O(NlogS)
- 查询操作的时候如何查找初符合x^u在范围内的数量是这道题的难点,可以采用前缀和的思想:找出 x ^ u<=high的u的数量r,满足 x ^u <=low-1 的数量l,则对于插入的第i个数,1-i-1 中满足的数为: r-l
- 问题转化为求x ^u<=limit 的u的个数,这种写法我一直wa掉,最后借鉴了大佬的<limit 的写法,这样写法很简明,以后建议使用
- 具体的Trie树操作可以去了解一下,这个数据结构处理异或的一些操作很有效
AC代码(带注释,方便复习)
class Solution {
public:
static const int N=20010*20;
int tr[N][2],cnt[N],idx=0;
void insert(int x)
{
int p=0;
for(int i=16;i>=0;i--){
int u=x>>i&1;
if(!tr[p][u]) tr[p][u]=++idx;
p=tr[p][u];
cnt[p]++;
}
}
int query(int x,int limit)
{
int p=0;
int res=0;
for(int i=16;i>=0;i--){
int u=x>>i&1,v=limit>>i&1;
//这种写法可以发现res累加的都是异或后小于limit的,等于的都没有选择,所以limit的范围要大一
//因为每次有两种选择时,我都是当前的cur与limit相等,
//所以每次累加的都是异或后<limit的,最后统计的总和也是异或后小于limit的,
if(u == 0 && v == 0) p = tr[p][0];
else if(u == 0 && v == 1) res += cnt[tr[p][0]], p = tr[p][1];
else if(u == 1 && v == 0) p = tr[p][1];
else if(u == 1 && v == 1) res += cnt[tr[p][1]], p = tr[p][0];
if(!p) break;
}
return res;
}
int countPairs(vector<int>& nums, int low, int high) {
int res = 0;
for(auto &x : nums) {
//这里要先查询在插入,因为x^x =0也是满足的
res += query(x, high+1) - query(x, low);
insert(x);
}
return res;
}
};