一.概念简介(为什么要用二分)
定义(看不懂哭死):
在计算机科学中,二分查找算法也称折半搜索算法,对数搜索算法,是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
二分法通常又叫二分查找,一般用于查找一个有序数组中的某个值的位置或者给定的特定值的插入位置;相比把整个数组遍历一次的O(n)复杂度,二分查找可以把复杂度降低到O(logn)。
无论是工作中,生活中,刷题时,查找都是非常常见的场合,很多同学对于数组中查找某一元素,第一反应都是线性查找,即for循环从nums[0]遍历到nums[n-1],这样时间复杂度是O(n),如果是使用二分法不断折半区间的方法,时间复杂度仅为O(logn),看似不高,当数据量较大时,logn复杂度会大大降低时间运行问题。
二.二分法写法
二分法写法有两种,但一种记住就够了,多记反而会导致记得太多而混淆。
二分法最重要的两个点:
- while循环中 left 和 right 的关系,到底是 left <= right 还是 left < right
- 迭代过程中 middle 和 right 的关系,到底是 right = middle - 1 还是 right = middle
一.第一种写法(左闭右闭)
每次查找的区间在[left, right](左闭右闭区间),根据查找区间的定义(左闭右闭区间),就决定了后续的代码应该怎么写才能对。因为定义 target 在[left, right]区间,所以有如下两点:
循环条件要使用while(left <= right),因为当(left == right)这种情况发生的时候,得到的结果是有意义的
if(nums[middle] > target) , right 要赋值为 middle - 1, 因为当前的 nums[middle] 一定不是 target ,需要把这个 middle 位置上面的数字丢弃,那么接下来需要查找范围就是[left, middle - 1]
int search(int nums[], int size, int target) //nums是数组,size是数组的大小,target是需要查找的值
{
int left = 0;
int right = size - 1; // 定义了target在左闭右闭的区间内,[left, right]
while (left <= right) { //当left == right时,区间[left, right]仍然有效
int middle = left + ((right - left) / 2);//等同于 (left + right) / 2,防止溢出
if (nums[middle] > target) {
right = middle - 1; //target在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; //target在右区间,所以[middle + 1, right]
} else { //既不在左边,也不在右边,那就是找到答案了
return middle;
}
}
//没有找到目标值
return -1;
}
首先看一个数组,需要对这个数组进行操作。需要对33进行查找的操作,那么target 的值就是33
- 首先,对 left 的值和 right 的值进行初始化,然后计算 middle 的值
left = 0, right = size - 1
middle = (left + (right - left) / 2 )
比较 nums[middle] 的值和 target 的值大小关系
if (nums[middle] > target),代表middle向右所有的数字大于target
if (nums[middle] < target),代表middle向左所有的数字小于target
既不大于也不小于就是找到了相等的值
nums[middle] = 13 < target = 33,left = middle + 1
-
循环条件为
while (left <= right)
-
此时,
left = 6 <= right = 11
,则继续进行循环 -
当前,
middle = left + ((right - left) / 2)
,计算出 middle 的值
-
计算出 middle 的值后,比较 nums[middle] 和 target 的值,发现:
- nums[middle] = 33 == target = 33,找到目标
二.第二种写法(左闭右开)
每次查找的区间在 [left, right),(左闭右开区间), 根据区间的定义,条件控制应该如下:
循环条件使用while (left < right)
if (nums[middle] > target), right = middle,因为当前的 nums[middle] 是大于 target 的,不符合条件,不能取到 middle,并且区间的定义是 [left, right),刚好区间上的定义就取不到 right, 所以 right 赋值为 middle。
int search(int nums[], int size, int target)
{
int left = 0;
int right = size; //定义target在左闭右开的区间里,即[left, right)
while (left < right) { //因为left = right的时候,在[left, right)区间上无意义
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle; //target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
// 没找到就返回-1
return -1;
}
三.例题(含代码)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
int h[N],w[N];
int n,k;
bool check(int mid){
int ans=0;
for(int i=1;i<=n;i++){
ans+=(h[i]/mid)*(w[i]/mid);//每一块巧克力能够取到的数量
}
if(ans>=k){
return true;//如果多或者刚好够人们分说明切的刚刚好或者小了
}else{
return false;//如果不够说明切大了
}
}
int main(){
cin>>n>>k;
int ma=0;
for(int i=1;i<=n;i++){
cin>>h[i]>>w[i];
ma=max(h[i],ma);
ma=max(w[i],ma);
}
int l=0;
int r=ma;
//第一种方法写的二分
while(l<=r){
int mid=(l+r)/2;
if(check(mid)){
l=mid+1;//注意加一
}else{
r=mid-1;//注意减一
}
}
cout<<r<<endl;//输出右边界
system("pause");
return 0;
}
完结撒花!!