一文秒杀二分查找所有题目

本文介绍了二分查找算法在解决吃瓜问题中的应用,包括基本的朴素二分查找、寻找满足条件的第一个瓜堆的左边界二分查找,以及针对右边界问题的二分查找策略。通过实例解析算法思想,并给出代码实现,帮助读者深入理解二分查找的变种及其在实际问题中的应用。

朴素二分查找

思想及实现

这是学算法的入门算法,先举个最简单的例子:
猜数字1:我心中默念一个数字,范围在1~100,你每次可以猜一个数,我会回答你大了,小了,直到猜中为止。
答:比如假设要猜的数字是38,整个过程如下:

	猜测范围(1,100),猜50,大了。所以比50大的都不用考虑了,包括50。
	猜测范围(1,49),猜25,小了。所以比25小的也不用考虑了,以此推论。
	猜测范围(26,49)猜37,小了。
	猜测范围(38,49)猜44,大了。
	猜测范围(38,43)猜40,大了。
	猜测范围(38,39)猜38,猜中了。

我们可以把这个过程抽象成代码:

int bin_search(){
	int num[100],L=0,R=100;//要猜的目标范围,左侧边界,右侧边界
	int obj=10,mid; //目标值,划分点
	while(L<=R){
		mid = (L+R) / 2;
		if(mid<obj) L = mid + 1; //小了,左侧变大
		else if (mid > obj) R = mid - 1; //大了,右侧变小
		else if (mid == obj) return mid;  //找到了
	}
	return -1;//没找到
}
几个重点:

二分查找必须应用于有序数据,默认为从小打大。朴素二分查找的数据不能重复,但可以不连续。

看到这里也许有人会说,如果我的obj都提前知道了还有什么意义呢?但是有时候,我们需要在一堆数据里面找到第一个满足要求的数据,比如这道题:

实战
原题:http://oj.haizeix.com/problem/386
吃瓜问题:有M堆瓜和N个群众,需要尽量满足每位群众吃瓜需求,但是为了避免浪费,分配的瓜不能多也不能少,如果没能找到就不分配。
输入:
​ 输入共 3 行。

​ 第一行两个整数 M,N。

​ 第二行 M 个整数分别表示 M堆瓜的数量。(保证各不相同)

​ 第三行 N 个整数分别表示 N个群众想吃的数量。(保证各不相同)

输出:
​ 对于每个 Yi 输出一行一个整数为对应数量的瓜的编号,若没有对应数量的瓜,则输出 0。
输入样例:
5 3
1 3 26 7 15
26 99 3
输出样例:
3
0
2

需要注意的是,每堆瓜我们不仅需要保存瓜的数量,也需要保存他的编号,因此可以用结构体来存储。
排序的时候也对整个结构体进行排序。
这道题可以分为3个步骤:
接收数据,排序,二分查找。直接上代码:

#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
using namespace std;

typedef struct node {
	int num; //编号
	int val; //瓜数量
} ;

int M, N;
node wm[100005];
//比较函数
bool cmp(const node &a, const node &b){
	return a.val < b.val;
}
//注意我们要比较瓜的数量,返回瓜的编号
int bin_search(int obj, int L, int R) {
	while (L <= R) {
		int mid = (L + R) / 2;
		if (wm[mid].val < obj) L = mid + 1;
		else if (wm[mid].val > obj) R = mid - 1;
		else if (wm[mid].val == obj) return wm[mid].num;
	}
	return 0;
}
int main() {

	cin >> M >> N; //M堆瓜,N个人

	for (int i = 1; i <= M; i++){
		
		cin >> wm[i].val;
		wm[i].num = i;
	}
	sort(wm + 1, wm + M + 1, cmp);
	
	int obj,L = 1, R = M;
	for (int i = 0; i < N; i++){
		cin >> obj;
		cout << bin_search(obj, L, R)  << endl; 
	}

	return 0;

}

特殊二分查找之左边界问题

思路及实现

还是上面得吃瓜问题,如果此时,我要求每个人分配的西瓜数量要大于等于他所需要的数量,那应该如何是好?
其实此类问题可以抽象为以下问题:
在000000111111中寻找第一个1。 0表示不满足条件的值,1表示满足条件的值。比如说我要吃20个西瓜,那你给我21个或者22个都是满足条件,但是为了节约,应该找出最小的。也就是21的编号。如果找不出来就返回0.

直接上题目吧:

原题:http://oj.haizeix.com/problem/387
吃瓜问题升级版:有M堆瓜和N个群众,需要尽量满足每位群众吃瓜需求,但是为了避免浪费,分配的应该是刚好大于等于需要的瓜的第一堆,如果所有数量都不能满足就不分配。
输入:
​ 输入共 3 行。

​ 第一行两个整数 M,N。

​ 第二行 M 个整数分别表示 M堆瓜的数量。(保证各不相同)

​ 第三行 N 个整数分别表示 N个群众想吃的数量。(保证各不相同)

输出
​ 对于每个 Yi 输出一行一个整数为大于等于需要数量的第一堆瓜的编号,若所有瓜堆的数量均小于需要数量,则输出 0。
输入样例:
5 5
1 3 26 7 15
27 10 3 4 2
输出样例:
0
5
2
4
2

有了上题的基础应该很好理解这题,需要注意的是,此题中,要求找不到返回0,因此我们可以在N中增加一堆瓜,数值设为超级大,序号设为0。这样其它条件都不满足,就一定会找到这堆瓜,返回的编号自然也就是0了。可以减少代码量。直接上代码:

#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
using namespace std;



typedef struct node {
	int num; //编号
	int val; //瓜数量
} ;

int M, N;
node wm[100005];

bool cmp(const node &a, const node &b){
	return a.val < b.val;
}
int binLeft_search(int obj, int L, int R) {
	while (L != R) {
		int mid = (L + R) / 2;
		//mid不满足条件
		if (wm[mid].val < obj) L = mid + 1 ;
		//mid满足条件,mid也可能是最终解,因此需要保留
		else if (wm[mid].val >= obj) R = mid ;
	}
	//此时L=R
	return wm[L].num;
}
int main() {

	cin >> M >> N; //M堆瓜,N个人

	for (int i = 1; i <= M; i++){
		scanf("%d",&wm[i].val);
		//cin >> wm[i].val;
		wm[i].num = i;
	}
	wm[0].num=0; //增加一堆超多的瓜
	wm[0].val=2100000000; 
	sort(wm , wm + M + 1, cmp);
	
	int obj,L = 0, R = M;
	for (int i = 0; i < N; i++){
		scanf("%d",&obj);
		cout << binLeft_search(obj, L, R)  << endl; 
	}

	return 0;

}
小试牛刀:

https://leetcode-cn.com/problems/first-bad-version/submissions/

特殊二分查找之右边界问题

右边界问题:11110000问题。见下方总结

关于左右边界的总结:

00001111问题(找左侧边界):
若二分找到的是0,说明此时要找的答案在右侧,所以左侧增大,L = mid + 1
若二分找到的是1,说明此时找到的答案在左侧或者自身,所以右侧减少,但是需要保护mid,R = mid.
1111000(找右侧问题):
若二分找到的是0,说明此时要找的答案在左侧,所以右侧减少,R = mid - 1
若二分找到的是1,说明此时找到的答案在右侧或者自身,所以左侧增大,但是需要保护midL = mid.
还但有一点不同:mid=(L+R+1)/2; 为什么要+1呢,这是为了mid的位置向右偏,否则可能会导致死循环:
若 L 指针指向 1 ,R 指向 0 时,若按照原来的公式mid = (L+R) /2,mid还是L,没有变化,因此会死循环。若+1,则此时mid 指向0,便可以收缩R。

记忆口诀:

朴素二分:
循环条件:左右可相等: L<=R
划分条件:取其正中间:(L+R)/ 2
左侧收缩:若小则升左: <,L=mid+1
右侧收缩:若大则降右: >,R=mid-1
终止条件:相等则退出: =,return mid

while(L<=N){
 	mid=(L+R)/2;
	if(N[mid]<t) L=mid + 1 ;
	else if(N[mid]<t) R=mid -1;
	else return mid;
}

右侧二分:00001111问题
循环条件:左右不相等 L != R
划分条件:取其正中间:(L+R)/ 2
先左收缩:若0则升左: <,L=mid+1
其他情况:右侧需保护。 R=mid

while(L!=R){
	mid = (L+R) / 2;
	if (N[mid] < t)  L = mid + 1; //找右,升左
	else R=mid; //保护右侧
}

左侧二分:111110000问题
循环条件:左右不相等 L != R
划分条件:加一再除二:(L+R+1)/ 2
先右收缩:若0则降右: >,R=mid-1
其他情况:左侧需保护。 L=mid

while(L!=R){
	mid=(L+R+1)/2; //防止死循环
	if(N[mid] < t) R = mid - 1; //小则降右
	else L=mid; //保护左侧
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值