二分查找要求:
线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。
适用情况
二分查找只适用顺序存储结构。为保持表的有序性,在顺序结构里插入和删除都必须移动大量的结点。因此,二分查找特别适用于那种一经建立就很少改动、而又经常需要查找的线性表。
对那些查找少而又经常需要改动的线性表,可采用链表作存储结构,进行顺序查找。链表上无法实现二分查找。
怎么写二分查找
求下界问题:
1.求大于等于target的第一个数;对应标准库中的lower_bound()函数。
2.求大于target的第一个数;对应标准库中的upper_bound()函数。
求上界问题: 等于对应上界问题的解-1
1.求小于target的最后一个数, 等于标准库中的lower_bound()函数的返回值减1。
2.求小于等于target的最后一个数, 等于标准库中的upper_bound()函数的返回值减1。
先看看参考:https://www.zhihu.com/question/36132386
关键点
1.根据你选择的区间是【】,还是【),
2.注意区分:循环条件,中值写法,是否加1,减1,循环结束后的状态
3.可以直接举几个实例看看,如2个元素,3个元素
1.左闭右闭区间【】
对应while(left<=right),
左中值:mid=left+(right-left)/2
循环结束时left>right,加1,减1
2.左闭右开【)
对应while(left<right),
右中值:mid=left+(right-left)/2
循环结束时left==right,加1,不用减1
参照标准库的lower_bound(),upper_bound()写法,倾向于选择【)来解题
头文件
<algorithm>
lower_bound()功能:返回大于等于val的迭代器
http://www.cplusplus.com/reference/algorithm/lower_bound/
- 默认版本
template <class ForwardIterator, class T>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val);
- 自定义比较函数版, 返回的是, comp(*迭代器, val)为true时,向右压缩区间;comp(*迭代器, val)为false时,向左压缩区间
template <class ForwardIterator, class T, class Compare>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
看下标准库的实现
template <class ForwardIterator, class T>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last, const T& val)
{
ForwardIterator it;
iterator_traits<ForwardIterator>::difference_type count, step;
count = distance(first,last);
while (count>0)
{
it = first; step=count/2; advance (it,step);
if (*it<val) { // or: if (comp(*it,val)), for version (2)
first=++it;
count-=step+1;
}
else count=step;
}
return first;
}
upper_bound()功能:返回大于val的迭代器
http://www.cplusplus.com/reference/algorithm/upper_bound/
- 默认版本
template <class ForwardIterator, class T>
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val);
- 自定义比较函数版, 返回的是, comp(*迭代器, val)为true时,向右压缩区间;comp(*迭代器, val)为false时,向左压缩区间
template <class ForwardIterator, class T, class Compare>
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
看下标准库的实现
template <class ForwardIterator, class T>
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last, const T& val)
{
ForwardIterator it;
iterator_traits<ForwardIterator>::difference_type count, step;
count = std::distance(first,last);
while (count>0)
{
it = first; step=count/2; std::advance (it,step);
if (!(val<*it)) // or: if (!comp(val,*it)), for version (2)
{ first=++it; count-=step+1; }
else count=step;
}
return first;
}
使用示例
// lower_bound/upper_bound example
#include <iostream> // std::cout
#include <algorithm> // std::lower_bound, std::upper_bound, std::sort
#include <vector> // std::vector
int main () {
int myints[] = {10,20,30,30,20,10,10,20};
std::vector<int> v(myints,myints+8); // 10 20 30 30 20 10 10 20
std::sort (v.begin(), v.end()); // 10 10 10 20 20 20 30 30
std::vector<int>::iterator low,up;
low=std::lower_bound (v.begin(), v.end(), 20); // ^
up= std::upper_bound (v.begin(), v.end(), 20); // ^
std::cout << "lower_bound at position " << (low- v.begin()) << '\n';
std::cout << "upper_bound at position " << (up - v.begin()) << '\n';
return 0;
}
动手写二分查找
- 左闭右开区间,实现二分查找
//二分查找
int binarySearch(vector<int> nums,int target){
int left=0,right=nums.size();//左闭右开
int mid;//右中值
//循环条件无等号
while(left<right){
mid=left+(right-left)/2;//右中值
if(nums[mid]==target)
return mid;//找到
else if(nums[mid]<target)
left=mid+1;
else
right=mid;//区间左闭右开,右边的取不到
}
return -1;//没有找到
}
- 左闭右开区间,实现myLower_bound()
//查找大于等于target的第一个数
int myLower_bound(vector<int> nums,int target){
int left=0,right=nums.size();//左闭右开
int mid;
//循环条件无等号
while(left<right){
mid=left+(right-left)/2;//右中值
if(nums[mid]<target)
left=mid+1;
else
right=mid;//区间左闭右开,右边的取不到
}
return left;
}
- 左闭右开区间,实现myUpper_bound()
//查找大于target的第一个数,
int myUpper_bound(vector<int> nums,int target){
int left=0,right=nums.size();
int mid;
while(left<right){
mid=left+(right-left)/2;
if(nums[mid]<=target)
left=mid+1;
else
right=mid;
}
return left;
}
完整测试
#include<iostream>
#include<algorithm>
#include<vector>
#include<time.h>
using namespace std;
//二分查找
int binarySearch(vector<int> nums,int target){
int left=0,right=nums.size();//左闭右开
int mid;//右中值
while(left<right){
mid=left+(right-left)/2;//右中值
if(nums[mid]==target)
return mid;//找到
else if(nums[mid]<target)
left=mid+1;
else
right=mid;//区间左闭右开,右边的取不到
}
return -1;//没有找到
}
//查找大于等于target的第一个数,举例【0,1,2】,target=0.1;
int myLower_bound(vector<int> nums,int target){
int left=0,right=nums.size();//左闭右开
int mid;
while(left<right){
mid=left+(right-left)/2;//右中值
if(nums[mid]<target)
left=mid+1;
else
right=mid;//
}
return left;
}
//查找大于target的第一个数,
int myUpper_bound(vector<int> nums,int target){
int left=0,right=nums.size();
int mid;
while(left<right){
mid=left+(right-left)/2;
if(nums[mid]<=target)
left=mid+1;
else
right=mid;
}
return left;
}
//测试
int main(){
vector<int> nums={1,2,2,2,2,3,3,4,5,5,6,7,7,8};
//随机选择一个数作为target
srand((unsigned int)time(NULL));
int index=rand()%nums.size();//0~size()-1
int target=nums[index];
//二分查找,只找到一个就返回,不一定是边界值
cout<<"binarySearch("<<target<<")= "<<binarySearch(nums,target)<<endl;
//找到大于等于target的第一个值的位置
cout<<"myLower_bound("<<target<<")= "<<myLower_bound(nums,target)<<endl;
//找到大于target的第一个值位置
cout<<"myUpper_bound("<<target<<")= "<<myUpper_bound(nums,target)<<endl;
//测试下lower_bound()和upper_bound()和equal_range()
cout<<"equal_range("<<target<<")=["<<(lower_bound(nums.begin(),nums.end(),target)-nums.begin())
<<","<<(upper_bound(nums.begin(),nums.end(),target)-nums.begin())<<")"<<endl;
cout<<"equal_range("<<target<<")=["<<equal_range(nums.begin(),nums.end(),target).first-nums.begin()
<<","<<equal_range(nums.begin(),nums.end(),target).second-nums.begin()<<")"<<endl;
system("pause");
return 0;
}
相关例题
剑指offer 面试题11:
旋转数组的最小数字
输入一个递增排序的数组的一个旋转,一个数组的旋转指把数组前面的若干位搬到后面
例如【3,4,5,1,2】是【1,2,3,4,5】的一个旋转
输出数组中的最小数字,这里就是1
思路
方法1:扫描一遍,同时判断当前位的数字大于当前位后一位时,返回当前位后一位的值,时间复杂度O(N)
#include<stdexcept>
int minInOrder(int* nums,int left,int right){
if(nums==nullptr||left<1||right<1||right<left)
throw logic_error("Invalid input");
if(nums[left]<nums[right])//原数组已经有序
return nums[left];
for(int i=left;i<right;i++){
if(nums[i]>nums[i+1])
return nums[i+1];
}
//到这里表明[left,right]中元素全相等
return nums[left];
}
方法2:
这里可以利用二分查找的思想,left,right,mid=left+(right-left)/2; 这里采用左闭右闭区间。
- 若num[mid]<=num[right],表明最小数字位于mid左边,即最小数字在区间【left,mid】
- 若num[mid]>=num[left],表明最小数字位于mid右边,即最小数字在区间【mid+1,right】
还有存在相等元素时的特殊情况【1,0,1,1,1】,【1,1,1,0,1】,此时无法知道最小数字位于mid左边还是右边,只能用方法1顺序扫描。
从这题看出来写好二分查找不容易,区间,有相等元素时的特殊情况【1,0,1,1,1】,【1,1,1,0,1】。
int getMin(int* nums,int length){
if(nums==nullptr||length<1)
throw logic_error("Invalid input");
//左闭右闭区间
int left=0,right=length-1;
int mid;
//注意【1,0,1,1,1】这种,所以不能取等号
if(nums[left]<nums[right])//原数组已经有序,
return nums[left];
//写二分查找时注意测试只有2个元素,只有3个元素的情况
//这里有一种特殊情况:【1,0,1,1,1】,【1,1,1,0,1】即数组里面出现了重复的元素
//表现为nums[left]==nums[right]==nums[mid],此时,我们无法判断最小元素在左边还是右边
//出现这种情况时我们必须在[left,right]内部顺序查找,
while(left<right){
mid=left+(right-left)/2;
//nums[left]==nums[right]==nums[mid],此时,我们无法判断最小元素在左边还是右边
//出现这种情况时我们必须在[left,right]内部顺序查找,
if(nums[left]==nums[mid]&&nums[right]==nums[mid])
return minInOrder(nums,left,right);
if(nums[mid]>=nums[left])
left=mid+1;//注意这里只有两个元素时避免死循环,必须加1,
else if(nums[mid]<=nums[right])
right=mid;
}
return nums[left];
}
爱吃香蕉的珂珂
leetcode 875. 爱吃香蕉的珂珂
https://leetcode-cn.com/problems/koko-eating-bananas/
珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
示例:piles[3,6,7,11],H=8;
输出:4
思路
1)先找出K的范围,[1,max(piles)]
2)K从1到max(piles)递增过程中一旦遇到H小时能吃完香蕉,就返回此时的k
3.延申到lower_bound()函数,这里就是返回区间中满足条件的首个位置
4.这里的将二分查找广义化了,原来的判断大小,压缩区间,这里变成了函数返回true/false来压缩区间
class Solution {
public:
//判断以速度K是否在H小时内能吃完
bool canFinish(vector<int>&piles,int K,int H){
int hours=0;
for(int i=0;i<piles.size();i++){
if(piles[i]<=K)
hours++;
else
{
hours+=(piles[i]/K +1);//piles[i]/K +1小时内吃完
}
}
return hours<=H;
}
int minEatingSpeed(vector<int>& piles, int H) {
//piles的最大值
int maxPiles=INT_MIN;
for(int i=0;i<piles.size();i++)
{
if(piles[i]>maxPiles)
maxPiles=piles[i];
}
int left=1,right=maxPiles+1;//区间左闭右开
while(left<right){
int mid=left+(right-left)/2;
if(canFinish(piles,mid,H))
right=mid;
else
{
left=mid+1;
}
}
return left;
}
};