理论分析
这边选择的是视频中的闭区间进行学习。
二分查找关键词:有序
寻找>=target的第一个数的下标:
int lower_bound(vector<int>nums, int target)
{
int left = 0;//闭区间left从0开始遍历
int right = nums.size() - 1;//闭区间right从nums.size()-1开始 [left,right]
while (left <= right)//保证区间有东西
{
int mid = left + (right - left) / 2;//处理溢出问题
if (nums[mid] < target)
left = mid + 1;//小于target 改变left为mid+1 保证可以退出循环
else
right = mid - 1;//大于target 改变right为mid-1 确保可以退出循环
}
return left;//最终找到的>=target的第一个数的下标 进行返回
}
扩展:对于需要找>target、<target、<=target的做法:
>target可以转换为寻找>=(target+1)的第一个数的下标,也就是lower_bound(nums,target+1)
<target可以转换为 寻找>=target的那个数 的左边的那个数:
比如对于数组:要找<8的数,那么我先找到>=8的数,也就是第一个8,然后再找它左边的那个数7,那么就找到了第一个<8(<target)的数
对于lower_bound来说那么就是lower_bound(nums,target)-1
<=target可以转换成 寻找>target的 左边那个数(>target)-1
那么对于lower_bound,就是lower_bound(nums,target+1)-1
题目回顾
题目一2300. 咒语和药水的成功对数 - 力扣(LeetCode)
通过代码:
class Solution {
public:
int lower_bound(vector<int>& nums, long long target)//二分查找
{
int left = 0;
int right = nums.size() - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
//这个时候的需要查找的target为success/position上取整 要注意的是测试例子的success会超出int范围,所以需要将tmp定义为long long
//找得到说明可以成功组合
vector<int> successfulPairs(vector<int>& spells, vector<int>& potions, long long success) {
vector<long long>spells_new(spells.size());
for (int i = 0; i < spells.size(); i++)
{
long long tmp = (success + spells[i]-1)/ spells[i];//上取整方法
spells_new[i] = tmp;
}//需要寻找的targetvector
vector<int>ans;
sort(potions.begin(), potions.end());//二分查找关键词:有序
for (int i = 0; i < spells_new.size(); i++)
{
int return_lower_bound = lower_bound(potions, spells_new[i]);//题目说的是>=直接用lower_bound
if (return_lower_bound != -1)
{
ans.push_back(potions.size() - 1 - return_lower_bound + 1);
}
else
{
ans.push_back(0);
}
}
return ans;
}
};
题解:2300. 咒语和药水的成功对数 - 力扣(LeetCode)-灵茶山艾府题解
class Solution {
public:
vector<int> successfulPairs(vector<int> &spells, vector<int> &potions, long long success) {
sort(potions.begin(), potions.end());
for (int &x : spells) {
long long target = (success - 1) / x;
if (target < potions.back()) {
// 这样写每次二分就只用 int 比较,避免把 potions 中的元素转成 long long 比较
x = potions.end() - upper_bound(potions.begin(), potions.end(), (int) target);
} else {
x = 0;
}
}
return spells;
}
};
题解中使用了upper_bound。
搜了下,C++中有对于二分查找的函数:lower_bound以及upper_bound。
都是利用二分查找的方法在一个排好序的数组中进行查找的。
摘一个评论区的总结:关于lower_bound( )和upper_bound( )的常见用法_lowerbound和upperbound-CSDN博客
// 升序数组中:
upper_bound(a.begin(), a.end(), x); // 查找第一个 > x的元素
lower_bound(a.begin(), a.end(), x); // 查找第一个 >= x的元素
// 降序数组中:
upper_bound(a.begin(), a.end(), x, greater<type>()); // 查找第一个 < x的元素
lower_bound(a.begin(), a.end(), x, greater<type>()); // 查找第一个 <= x的元素
题目二2389. 和有限的最长子序列 - 力扣(LeetCode)
这题花的时间比较久,写着写着逻辑很混乱,最后是去看了题解才通过的,主要问题集中在不会进行前缀和的计算以及找到二分查找的target。
通过代码:
class Solution {
public:
int lower_bound(vector<int>& nums, int target)
{
int left = 0;
int right = nums.size() - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
vector<int> answerQueries(vector<int>& nums, vector<int>& queries) {
vector<int>ans;
sort(nums.begin(), nums.end());//排序 二分查找前提 1,2,4,5
printf("\nsort:\n");
for (int i = 1; i < nums.size(); i++)
nums[i] += nums[i - 1];//进行原地前缀和的计算
for (auto& e : queries)//进行完原地前缀和的计算之后,那么测试用例就变成了 1,3,7,12
//那么根据题目要求,需要找到<=queries[i]的,最大子序列,那么只需要前缀和<=queries[i]的
//也就是<=target lower_bound(nums,e+1)-1 eg:>=4 ->left=2 -1->3(下标1) lower_bound=->1
//又因为要求的是个数1-0+1
ans.push_back(lower_bound(nums, e + 1));
return ans;
}
};
题解:2389. 和有限的最长子序列 - 力扣(LeetCode)-灵茶山艾府题解
class Solution {
public:
vector<int> answerQueries(vector<int> &nums, vector<int> &queries) {
sort(nums.begin(), nums.end());
for (int i = 1; i < nums.size(); ++i)
nums[i] += nums[i - 1]; // 原地求前缀和
for (int &q : queries) // 复用 queries 作为答案
q = upper_bound(nums.begin(), nums.end(), q) - nums.begin();
return queries;
}
};
使用了upper_bound(寻找<=target的最后一个数那么就意味着找>x的第一个数的下标)
题目三2563. 统计公平数对的数目 - 力扣(LeetCode)
这题的执行内存和时间都花的比较多。没必要看这么没效率的代码了,直接看题解。
通过代码:
class Solution {
public:
int lower_bound(vector<int>& nums, int target,int k)
{
int left = k+1;
int right = nums.size() - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
printf("left=%d\n", left);
return left;
}
long long countFairPairs(vector<int>& nums, int lower, int upper) {
long long ans = 0;
sort(nums.begin(), nums.end());
for (auto& e : nums)
{
printf("%d ", e);
}
printf("\n");
for (int i = 0; i < nums.size(); i++)
{
int target1 = lower - nums[i];
int target2 = upper - nums[i];
//if (target1 < 0||target2<0)
//break;
int left_lower = lower_bound(nums, target1,i);
int right_lower = lower_bound(nums, target2 + 1,i) - 1;
//if (left_lower <= i || right_lower <= i)
//break;
ans = ans + (right_lower - left_lower + 1);
}
return ans;
}
};
题解:2563. 统计公平数对的数目 - 力扣(LeetCode)
class Solution {
public:
long long countFairPairs(vector<int> &nums, int lower, int upper) {
long long ans = 0;
ranges::sort(nums);
for (int j = 0; j < nums.size(); ++j) {
auto r = upper_bound(nums.begin(), nums.begin() + j, upper - nums[j]); // <= upper-nums[j] 的 nums[i] 的个数
auto l = lower_bound(nums.begin(), nums.begin() + j, lower - nums[j]); // < lower-nums[j] 的 nums[i] 的个数
ans += r - l;
}
return ans;
}
};
题解把问题转换为求nums[i]的范围,求出了范围里面数的个数就得到了结果
更新:跟着题解修改:
class Solution {
public:
long long countFairPairs(vector<int>& nums, int lower, int upper) {
long long ans = 0;
sort(nums.begin(), nums.end());
for (int j = 0; j < nums.size(); j++)//通过遍历j的方法找i,保证i在j前面
{
//upper_bound和lower_bound返回的是一个迭代器
auto right_lower = upper_bound(nums.begin(), nums.begin()+j,upper - nums[j])-1;//找<=upper-nums[j]的nums[i]
auto left_lower = lower_bound(nums.begin(), nums.begin()+j,lower - nums[j]);//<转换成>=
ans = ans + (right_lower - left_lower + 1);
}
return ans;
}
};