二分查找学习总结

理论分析

视频讲解:二分查找 红蓝染色法_哔哩哔哩_bilibili

这边选择的是视频中的闭区间进行学习。

二分查找关键词:有序

寻找>=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;
}
};

  • 29
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值