【字典树(前缀树) 位运算】1803. 统计异或值在范围内的数对有多少

本文涉及知识点

字典树(前缀树) 位运算

LeetCode1803. 统计异或值在范围内的数对有多少

给你一个整数数组 nums (下标 从 0 开始 计数)以及两个整数:low 和 high ,请返回 漂亮数对 的数目。
漂亮数对 是一个形如 (i, j) 的数对,其中 0 <= i < j < nums.length 且 low <= (nums[i] XOR nums[j]) <= high 。
示例 1:
输入: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
示例 2:
输入:nums = [9,8,4,2,1], low = 5, high = 14
输出:8
解释:所有漂亮数对 (i, j) 列出如下:
​​​​​ - (0, 2): nums[0] XOR nums[2] = 13
- (0, 3): nums[0] XOR nums[3] = 11
- (0, 4): nums[0] XOR nums[4] = 8
- (1, 2): nums[1] XOR nums[2] = 12
- (1, 3): nums[1] XOR nums[3] = 10
- (1, 4): nums[1] XOR nums[4] = 9
- (2, 3): nums[2] XOR nums[3] = 6
- (2, 4): nums[2] XOR nums[4] = 5
提示:
1 <= nums.length <= 2 * 104
1 <= nums[i] <= 2 *104
1 <= low <= high <= 2 * 104

字典树

各节点记录各子树节点的数量。
n = nums.length
For i : 0 < n {
ret += f(nums[i],hight)- f(nums[i],low-1)
}
由于low>0,所有无需考虑low为0的情况。
f(nums[i],x)函数 :node ⊕ \oplus nums[i] <=x 的数量。node 是叶子节点对应的值。

从高位到低位依次处理x,curBit是nums[i]的此二进制位。 p指向要处理的子树,初始指向根。
{ c n t + = p − > c h i l d s [ c u r B i t ] , p = p − > c h i l d s [ ! c u r B i t ] 。 x 此位是 1 p = p − > c h i l d s [ c u r B i t ] 。 e l s e \begin{cases} cnt += p->childs[curBit],p = p->childs[!curBit]。 && x此位是1 \\ p = p->childs[curBit]。&& else \\ \end{cases} {cnt+=p>childs[curBit]p=p>childs[!curBit]p=p>childs[curBit]x此位是1else
结束是,如果p不为空,则说明p等于x。
无论如何,每个x的二进制位都只需要处理一个。故:处理nums[i]的时间复杂度是O(logn)。
总时间复杂度是:O(nlogn)。

字典树代码

class C2BNumTrieNode
{
public:
	C2BNumTrieNode()
	{
		m_childs[0] = m_childs[1] = nullptr;
	}
	bool GetNot0Child(bool bFirstRight)
	{
		auto ptr = m_childs[bFirstRight];
		if (ptr && (ptr->m_iNum > 0))
		{
			return bFirstRight;
		}
		return !bFirstRight;
	}
	int m_iNum = 0;
	C2BNumTrieNode* m_childs[2];
};

template<class T = int, int iLeveCount = 31>
class C2BNumTrie
{
public:
	C2BNumTrie()
	{
		m_pRoot = new C2BNumTrieNode();
	}
	void  Add(T iNum)
	{
		m_setHas.emplace(iNum);
		C2BNumTrieNode* p = m_pRoot;
		for (int i = iLeveCount - 1; i >= 0; i--)
		{
			p->m_iNum++;
			bool bRight = iNum & ((T)1 << i);
			if (nullptr == p->m_childs[bRight])
			{
				p->m_childs[bRight] = new C2BNumTrieNode();
			}
			p = p->m_childs[bRight];
		}
		p->m_iNum++;
	}
	void Del(T iNum)
	{
		auto it = m_setHas.find(iNum);
		if (m_setHas.end() == it)
		{
			return;
		}
		m_setHas.erase(it);
		C2BNumTrieNode* p = m_pRoot;
		for (int i = iLeveCount - 1; i >= 0; i--)
		{
			p->m_iNum--;
			bool bRight = iNum & ((T)1 << i);
			p = p->m_childs[bRight];
		}
		p->m_iNum--;
	}	
	void Swap(C2BNumTrie<T, iLeveCount>& o) {
		swap(m_pRoot, o.m_pRoot);
		swap(m_setHas, o.m_setHas);
	}
	C2BNumTrieNode* m_pRoot;
	std::unordered_multiset<T> m_setHas;
};

template<class T = int, int iLeveCount = 31>
class CXorLessEqual2BTrie : public C2BNumTrie<T, iLeveCount>
{
public:
	int Count(T iXor,int lessEqualValue)
	{
		C2BNumTrieNode* p = C2BNumTrie<T, iLeveCount>::m_pRoot;
		int cnt = 0;
		for (int i = iLeveCount - 1; p && (i >= 0); i--)
		{
			const bool maxBit = lessEqualValue & ((T)1 << i);		
			const bool curBit = iXor & ((T)1 << i);
			if (maxBit) {				
				auto ptr = p->m_childs[curBit];
				if (nullptr != ptr) { cnt += ptr->m_iNum; }
				p = p->m_childs[!curBit];
			}
			else { p = p->m_childs[curBit]; }
		}
		if (nullptr != p) { cnt += p->m_iNum; }
		return cnt;
	}
};

class Solution {
public:
	int countPairs(vector<int>& nums, int low, int high) {
		CXorLessEqual2BTrie trie;
		int iRet = 0;
		for (const auto& n : nums) {
			iRet += trie.Count(n,high) - trie.Count(n,low-1);
			trie.Add(n);
		}
		return iRet;
	}
};

测试用例

template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
	if (v1.size() != v2.size())
	{
		assert(false);
		return;
	}
	for (int i = 0; i < v1.size(); i++)
	{
		assert(v1[i] == v2[i]);
	}
}

template<class T>
void Assert(const T& t1, const T& t2)
{
	assert(t1 == t2);
}

int main()
{
	vector<int> nums;
	int low,  high;
	{
		Solution slu;
		nums = { 1, 4, 2, 7 }, low = 2, high = 6;
		auto res = slu.countPairs(nums, low, high);
		Assert(6, res);
	}
	{
		Solution slu;
		nums = { 9,8,4,2,1 }, low = 5, high = 14;
		auto res = slu.countPairs(nums, low, high);
		Assert(8, res);
	}
	
}

2023年4月版

class C2BNumTrieNode
{
public:
C2BNumTrieNode()
{
m_childs[0] = m_childs[1] = nullptr;
}
bool GetNot0Child(bool bFirstRight)
{
auto ptr = m_childs[bFirstRight];
if (ptr && ( ptr->m_iNum >0 ))
{
return bFirstRight;
}
return !bFirstRight;
}
int m_iNum = 0;
C2BNumTrieNode* m_childs[2] ;
};

template
class C2BNumTrie
{
public:
C2BNumTrie()
{
m_pRoot = new C2BNumTrieNode();
}
void Add(int iNum)
{
C2BNumTrieNode* p = m_pRoot;
for (int i = iLeveNum - 1; i >= 0; i–)
{
p->m_iNum++;
bool bRight = iNum & (1 << i);
if (nullptr == p->m_childs[bRight])
{
p->m_childs[bRight] = new C2BNumTrieNode();
}
p = p->m_childs[bRight];
}
p->m_iNum++;
}
C2BNumTrieNode* m_pRoot;

};

class Solution {
public:
int countPairs(vector& nums, int low, int high) {
int iRet = 0;
for (const int& n : nums)
{
iRet += Count(n, high);
iRet -= Count(n, low - 1);
m_nt.Add(n);
}
return iRet;
}
int Count(const int n,int iMax)
{
int iCount = 0;
auto p = m_nt.m_pRoot;
for (int i = 15 - 1; i >= 0; i–)
{
bool bMaxOne = iMax & (1 << i);
bool bNOne = n & (1 << i);
if (bMaxOne)
{
if (p->m_childs[bNOne])
{
iCount += p->m_childs[bNOne]->m_iNum;
}
p = p->m_childs[!bNOne];
}
else
{
p = p->m_childs[bNOne];
}
if (nullptr == p)
{
return iCount;
}
}
iCount += p->m_iNum;
return iCount;
}
C2BNumTrie<15> m_nt;
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
《喜缺全书算法册》以原理、正确性证明、总结为主。
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值