二分查找算法--刷题

本文详细介绍了二分查找在不同编程问题中的应用,如有序数组中查找最先出现的数字、A-B数对的变形题目、砍树问题、木材加工问题、一元三次方程解的精确查找以及跳石头和路标设置问题。每个例子都展示了如何使用二分查找优化查找过程并确定正确区间。
摘要由CSDN通过智能技术生成

目录

简介:

模板:​编辑

一:二分查找最先出现的数字​编辑

code: 

二:A-B数对---查找某个值的变形题目 ​编辑

code:

三 :砍树问题---面向结果验证答案是否符合要求

code: 

四:木材加工---面向结果验证答案

 code:

五:一元三次方程---二分缩小查找区间(精确浮点数)

code:

六: 跳石头---最小值最大问题

code: 

七:路标设置---最大值最小问题 

code: 


简介:

二分查找也常被称为二分法或者折半查找,每次查找时通过将待查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少。对于一个长度为 O(n) 的数组,二分查找的时间复杂度为 O(log n)。实质是验证位置是否符合要求,将数组分为符合要求的区间与不符的区间,找到临界位置。

但值得注意的是:二分查找主要针对的是有序数组。

模板:

对于一个长度为N,区间为【0,N-1】的数组,可以分别设置左,右为边界位置的前后一个点,在按照模板将区间划分成符合要求的两个区块,最后按 check函数的设置决定取哪个点进行算法。这个方法重要的一步是检查最后返回的left或right是否越界,依次来判定返回值。

详情的模板解释可以去看B站五点七边的视频,这里主要进行刷题的讲解。

一:二分查找最先出现的数字

 对于有序数组的查找,最先想到二分查找,IsBule的条件可以写为:要验证的位置是否小于所要查找的值,是则将该验证位置+其左边的数组全部置为小于查找值的区间。最后只要验证right位置的值是否为要查找的值,是则返回right,否则返回-1;

code: 

#include<iostream>
using namespace std;

int SearchNum(int*a,int val,int size)
{
	int left = -1, right = size;
	while (left + 1 != right)
	{
		int mid = (left + right) / 2;
		if (a[mid] < val)
		{
			left = mid;
		}
		else
		{
			right = mid;
		}
	}
	if (right!=size&&a[right] == val)
		return right+1;//返回下标
	else
		return -1;
}


int main()
{
	int n, m;
	cin >> n >> m;
	int* a = new int[n];
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	while (m--)
	{
		int x;
		cin >> x;
		printf("%d ", SearchNum(a,x,n));
	}
	return 0;
}

二:A-B数对---查找某个值的变形题目 

对于A-B=C的数对,实际上可以看成B+C=A的数对个数,这里先将数组排列有序,for循环遍历赋值给B,实际则查找数组中B+C数值点的个数,二分查找即可。可以先查找A第一次出现的位置,在查找A出现的最后一次的位置,两者相减+1则得到A的个数,注意验证A不存在的情况。

code:

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

int first_A(int* a, int A, int size)
{
	int left = -1, right = size;
	while (left + 1 != right)
	{
		int mid = (left + right) / 2;
		if (a[mid] < A)
		{
			left = mid;
		}
		else
		{
			right = mid;
		}
	}
	if (right!=size&&a[right] == A)
		return right;
	else
		return 0;
}
int last_A(int* a, int A, int size)
{
	int left = -1, right = size;
	while (left + 1 != right)
	{
		int mid = (left + right) / 2;
		if (a[mid] <= A)
		{
			left = mid;
		}
		else
		{
			right = mid;
		}
	}
	if (left!=-1&&a[left] == A)
		return left;
	else
		return 0;
}


long long A_B_num(int*a, int val, int size)
{
	long long sum = 0;
	sort(a, a + size);
	for (int B = 0; B < size; B++)
	{
		int A = a[B] + val;
		if (first_A(a, A, size) && last_A(a, A, size))
		{
			sum += last_A(a, A, size) - first_A(a, A, size) + 1;
		}
	}
	return sum;
}
int main()
{
	int n, c;
	cin >> n >> c;
	int* a = new int[n];
	for (int i = 0; i < n; i++)
		cin >> a[i];
	cout << A_B_num(a,c,n) << endl;
	return 0;
}

三 :砍树问题---面向结果验证答案是否符合要求

先记录最高树木的高度为highest,要查找的最大高度一定在【1,highest】之间,所以用二分查找验证mid高度是否符合要求。这里要查找的数组实际是答案数组,只要找出符合题目要求的区间取最大值即可。

code: 

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

//size--树的个数,need--需要的木头高度,pos--当前要砍的高度位置
bool is_MoreNeed(int* a, int size, int need,int pos)
{
	int sum = 0;
	for (int i=0;i<size;i++)
	{
		sum += max(0, a[i] - pos);
		if (sum >= need)
		{
			return true;
		}
	}
	return false;
}

int Height_Max(int* a, int size, int need,int highest)
{
	int left = 0, right = highest;
	while (left + 1 != right)
	{
		int mid = (left + right) / 2;
		if (is_MoreNeed(a,size,need,mid))
		{
			//寻找临界高度
			left = mid;
		}
		else
		{
			right = mid;
		}
	}
	return left;
}

int main()
{
	int n, m;
	cin >> n >> m;
	int* a = new int[n];
	int highest = 0;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
		highest = max(highest, a[i]);//记录最高树的高度
	}
	cout<<Height_Max(a,n,m,highest)<<endl;
	return 0;
}

四:木材加工---面向结果验证答案

求加工出的木材最大长度一定在【0,longest】长度之间,只需验证每次二分的mid是否符合题目要求即可(验证加工出的木材是否大于等于k)。 

 code:

#include<iostream>

#include<algorithm>
using namespace std;

int longest = 0;
//pos--目标切割长度
bool More_Num(int *a,int size,int need,int pos)
{
	int sum = 0;
	for (int i = 0; i < size; i++)
	{
		sum += a[i] / pos;
		if (sum >= need)
		{
			return true;
		}
	}
	return false;
}

//need--需要的等长的木材个数,size--总木材个数
int LongestNum(int*a,int size,int need,int longest)
{
	//答案在【left,right】区间内,找到临界位置即可
	int left = 0, right = longest;
	while (left + 1 != right)
	{
		int mid = (left + right) / 2;
		if (More_Num(a,size,need,mid))
		{
			left = mid;
		}
		else
		{
			right = mid;
		}
	}
	return left;
}

int main()
{
	int n, k;
	cin >> n >> k;
	int* a = new int[n];

	for (int i = 0; i < n; i++)
	{
		//记录每个木头的长度
		cin >> a[i];
		longest = max(longest, a[i]);
	}
	cout << LongestNum(a,n,k,longest) << endl;
	return 0;
}

五:一元三次方程---二分缩小查找区间(精确浮点数)

 依次验证【-100,-99】,【-99,-98】...【99,100】等区间有没有所求根---利用提示写判定条件,当验证出在某个小区间内时,令left,right分别为左右边界,利用二分查找验证根是否在mid,right之间,是则令left=mid,否则令right=mid;精度尽量小一点便于找精确值。

code:

#include<cstdio>
double a,b,c,d;
double fc(double x)
{
    return a*x*x*x+b*x*x+c*x+d;
}
int main()
{
    double l,r,m,x1,x2;
    int s=0,i;
    scanf("%lf%lf%lf%lf",&a,&b,&c,&d);  //输入
    for (i=-100;i<100;i++)
    {
        l=i; 
        r=i+1;
        x1=fc(l); 
        x2=fc(r);
        if(!x1) 
        {
            printf("%.2lf ",l); 
            s++;
        }      //判断左端点,是零点直接输出。
                        
                        //不能判断右端点,会重复。
        if(x1*x2<0)                             //区间内有根。
        {
            while(r-l>=0.000001)                     //二分控制精度。
            {
                m=(l+r)/2;  //middle
                if(fc(m)*fc(r)<=0) 
                   l=m; 
                else 
                   r=m;   //计算中点处函数值缩小区间。
            }
            printf("%.2lf ",r);  
            //输出右端点。
            s++;
        }
        if (s==3) 
            break;             
            //找到三个就退出大概会省一点时间
    }
    return 0;
}

六: 跳石头---最小值最大问题

因为答案肯定还是在【0,L】之间,所以还是面向答案二分验证答案是否符合要求。

这里因为是求最小值的尽可能大值,所以left区间是符合要求的,只需找到最大值left即可。

check方法是保证石头间隔小于等于mid验证值,不然则搬走一块石头,cnt++,最后要搬的石头数小于等于要求数即说明验证值符合left区间,以此划分区间即可。

code: 

#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;

//size--石头个数,pos--要验证的答案
bool check(int*a,int size,int pos,int need)
{
	//目的是验证该最大距离是否符合要求,即要使最小距离大于等于目标pos,计算搬走的石头是否小于等于要求的次数M。
	//now--现在站立的位置,i--要判断是否搬走的石头
	int i = 0, now = 0;
	int cnt = 0;
	while (i <= size)
	{
		i++;
		if (a[i] - a[now] < pos)
		{
			//搬走i处的石头
			cnt++;
			continue;
		}
		else
		{
			now = i;
		}
	}
	if (cnt <= need)
		return true;
	else
		return false;
}

int main()
{
	int L, N, M;
	cin >> L >> N >> M;
	int* a = new int[N + 10];
	memset(a, 0, sizeof(a));
	for (int i = 1; i <= N; i++)
	{
		cin >> a[i];
	}
	a[N + 1] = L;
	int left = 0, right = L + 1;
	while (left + 1 != right)
	{
		int mid = (left + right) / 2;
		if (check(a,N,mid,M))
		{
			left = mid;//左边是符合条件的
		}
		else
		{
			right = mid;
		}
	}
	cout << left<< endl;
	return 0;
}

七:路标设置---最大值最小问题 

 答案在(-1,L+1)之间所以面向答案验证。left=-1,right=L+1;

这里是求最大值尽可能小,所以right区间符合要求,则求最小的right值即可。

check方法是保证所有间隔距离小于等于验证Mid,如果大于则增设一个路标,cnt++;

最后如果增设数小于等于要求的K,则符合right区间,否则不符。

code: 


#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;


const int N = 100010;
int q[N];


bool check(int pos,int size,int need)
{
	int now = 1, i = 2;
	int cnt = 0;
	int a[N];
	memcpy(a, q,sizeof(q));
	while(i<=size)
	{
		if (a[i] - a[now] > pos)
		{
			a[now] += pos;
			cnt++;
		}
		else
		{
			now = i;
			i++;
		}
	}
	if (cnt > need)
		return false;
	else
		return true;
}

int main()
{
	int L, N, K;
	cin >> L >> N >> K;
	for (int i=1;i<=N;i++)
	{
		cin >> q[i];
	}
	int left = -1, right = L+1;
	while (left + 1 != right)
	{
		int mid = (left + right)/2;
		if (!check(mid,N,K))
		{
			left = mid;
		}
		else
		{
			right = mid;
		}
	}
	cout << right << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值