力扣刷题笔记
力扣每日一题1.5 统计异或值在范围内的数对有多少
前言
力扣每日一题1.5(hard),统计异或值在范围内的数对有多少,题目:
给你一个整数数组nums(下表从0开始计数)以及两个整数low和high,请返回漂亮数对的数目。
漂亮数对是一个形如(i,j)的数对,其中0 <= i < j < nums.length且 low <= (nums[i] XOR nums[j]) <= high。
1 <= nums.length <= 2 * 10^4
1 <= nums[i] <= 2 * 10^4
1 <= low <= high <= 2 * 10^4
一、题目分析
本题的解题思路目前比较简单,就是按照题意,遍历数组,看两两组队的元素数对是否符合漂亮数对
二、解题方法
method1:遍历数组,检查每两个元素数据对是否符合漂亮数对的条件
注意:位运算符(亦或^)优先级比比较运算符(<=)优先级低,因此需要注意括号的使用
代码如下:
class Solution {
public:
int countPairs(vector<int>& nums, int low, int high) {
int size = nums.size();
int res = 0;
for(int i = 0;i < size - 1;i++){
for(int j = i + 1;j < size;j++){
// cout << (nums[i] ^ nums[j]) << endl;
if((nums[i] ^ nums[j]) >= low && (nums[i] ^ nums[j]) <= high){
res++;
}
}
}
return res;
}
};
提交后超过时间限制,超过时间限制截图如下图所示:时间复杂度为O(n^2),n为数组的长度,因此需要降低时间复杂度。
因此,method1方法不能采用
method2.字典树
template<class T,size_t N> class array;,c++中array的通用格式为array<类型名,元素个数>数组名
由于之前对字典树的使用比较少,因此在这里详细分析一下
先对于时间复杂度的分析,由于nums <= 2* 10^4,因此总共只需要15位二进制数即可满足( 2^14=16384 ;2^15=32768)
本方法的思路:
- 判断ai和aj是否属于漂亮数对是看ai和aj的亦或值是否在low和high之间,字典树判断是否在一个范围之间比较复杂,因此判断数组中两个元素的异或值<=high的个数减去异或值<low的个数比较简单。
分解后的的问题:判断某个数组中两个元素的异或值小于给定数值的个数,如若满足,则称为美丽数对
- 对于分解后的问题的解决方案(利用字典树来解决):
首先:从第二个元素开始,判断该元素与每个之前的元素是否满足美丽数对,
之所以从第二个元素开始,是方便将元素一个个的插入字典树中,即先插入第一个元素,再插入第二个......最后插入倒数第二个元素到字典树中
。
其次构造字典树,即字典树每个结点有两个属性值,一个是指向子结点的数组指针(分别代表下一位是0还是1)以及目前已插入结点拥有该前缀的元素个数sum。
最后,构造两个方法,分别为add方法,即将元素插入字典树中;以及get方法,即针对某一元素ai,看ai之前的元素与ai是美丽数对的个数。 - 合并问题,将每个元素ai是美丽数对的个数累加。最后再用high的美丽数对的个数减去low-1的美丽数对的个数,即可求出最后需要得出的值。
可以看出,该方法的时间复杂度为O(n*logc),n为数组的长度,c为数组单个元素的大小,即15 > logc > 0,可以得出该方法和method1比起来时间复杂度降低一个量级
代码如下:
//构建字典树结点结构
struct Tire{
//初始化每个结点的指针数组,child[0]代表左子树,child[1]代表右子树
array<Tire*,2> child{nullptr,nullptr};
int sum;
//等同于Tire():sum(0){}
Tire(){sum = 0;}
};
class Solution {
private:
//字典树的根结点
Tire* root = nullptr;
//定义静态常量,最高位为14位(位数从0开始)
static constexpr int HIGHBIT = 14;
public:
void add(int elementVal){
Tire* cur = root;//获取根结点指针
for(int k = HIGHBIT;k >= 0;k--){
int bit = (elementVal >> k) & 1;
if(cur -> child[bit] == nullptr){
cur->child[bit] = new Tire();
}
cur = cur->child[bit];
cur->sum++;
}
}
//注意该函数只是针对某一个ai
int get(int num,int x){
Tire* cur = root;
int sum = 0;
//遍历的是位数
for(int k = HIGHBIT;k >= 0;k--){
int r = (num >> k) & 1;
if((x >> k) & 1){
if(cur->child[r] != nullptr){
sum += cur->child[r]->sum;
}
if(cur->child[r ^ 1] == nullptr){
return sum;
}
cur = cur->child[r ^ 1];
}else{
if(cur->child[r] == nullptr){
return sum;
}
cur = cur->child[r];
}
}
sum += cur->sum;
return sum;
}
int f(vector<int>& nums,int x){
//初始化根结点,根结点的sum无用
root = new Tire();
int res = 0;
for(int i = 1;i < nums.size();i++){
//从小到大,把元素加入字典树中,即判断a[i]和(a[0]到a[i-1])是否是漂亮数对
add(nums[i-1]);
//对于每个ai,判断有多少个漂亮数对(ai,aj)(j < i)
res += get(nums[i],x);
}
return res;
}
int countPairs(vector<int>& nums, int low, int high) {
//f函数表示在第一个参数nums数组中多少对元素的亦或运算结果小于等于第二个参数
return f(nums,high) - f(nums,low-1);
}
};
三、每日一题1.6
题目:统计各位数字之和为偶数的整数个数(easy)
暴力枚举即可,代码如下所示:
class Solution {
public:
//各位是指个、十、百、千位
bool isOushu(int num){
int tempNum = 0;
while(num != 0){
tempNum += num % 10;
num = num/10;
}
if(tempNum % 2 == 0){
return true;
}
return false;
}
int countEven(int num) {
int res = 0;
for(int i = 1;i <= num;i++){
if(isOushu(i)){
res++;
}
}
return res;
}
};
总结
1.5的每日一题难度较大,字典树的使用也比较陌生,后续仍需加强对该方面题型的学习和巩固;1.6比较简单,之间暴力枚举即可解决。