引言
不论你是找工作还是考研,不论是什么专业(当然首先要是计算机大类的哈),不论是参加笔试和面试,二分这个问题是必考的,而且非常有可能会让你手撕代码(就是给你一张
A
4
A4
A4 纸让你把代码手写出来),所以这个二分的重要性不言而喻,我这里总结了我目前掌握的所有二分的一个写法,包括两个版本,而且也详细介绍了递归和非递归的模板,其实学习这些模板首先应该理解,然后把它背过,并且之后要反复的重复大量的练习(这才是根本),这样这个东西就跟你背九九乘法表一样,融进你的血液里了,成了你生命中的一部分吧,最后大概就是这么个思想,然后一些比较难的竞赛题,其实也是把各个知识点总结起来了。
为什么要用二分?其实就是快,时间复杂度从
O
(
n
)
O(n)
O(n) 降到
O
(
l
o
g
n
)
O(logn)
O(logn) ,当然前提是排好序了
啥时候用二分?当满足单调性,只要满足条件的在一个范围里,要找到这个范围的边界点
一、二分模板
1.非递归模板
int BinarySearch01(int nums[], int left, int right, int key) //非递归写法
{
while (left <= right)
{
int mid = (left + right) / 2;
//int mid = (right - left) / 2 + left; 这里用减法是因为加法可能会爆int,用减法概率就会小很多
// int mid = left + right >> 1; 这里也只是写法不同
if (nums[mid] > key)
{
right = mid - 1;
}
else if (nums[mid] < key)
{
left = mid + 1;
}
else
{
return mid;
}
}
return -1; //代表无该值
}
2.递归模板
int BinarySearch02(int nums[], int left, int right, int key) //递归写法
{
if (left > right) return -1;
int mid = left + right >> 1;
if (nums[mid] > key) return BinarySearch02(nums, left, mid - 1, key);
else if (nums[mid] < key) return BinarySearch02(nums, mid + 1, right, key);
return mid;
}
3.二分通用模板
这里的这个通用模板可以解决所有的二分问题
int BinarySearch03(int nums[], int left, int right, int key) //通用模板
{
int l = left, r = right;
while (l < r)
{
int mid = l + r >> 1; //要变条件这里就得变成int mid = l + r + 1 >> 1;
if (nums[mid] >= key) r = mid; //当然这里写成<=也是对的,不过条件就得变成l = mid,原因见下文部分三
else l = mid + 1; //这里就得变成r = mid - 1
}
if (nums[l] != key) return -1;
return l; //l和r是一样的
}
4.测试
所得结果全部正确
int main()
{
int nums[10] = { 1,2,3,4,5,6,7,8,9,10 };
int n = sizeof(nums) / sizeof(nums[0]);
//cout << BinarySearch01(nums, 0, n - 1, 1) << endl;
//cout << BinarySearch01(nums, 0, n - 1, 10) << endl;
//cout << BinarySearch01(nums, 0, n - 1, 3) << endl;
//cout << BinarySearch01(nums, 0, n - 1, 7) << endl;
//cout << BinarySearch01(nums, 0, n - 1, 11) << endl;
//cout << BinarySearch01(nums, 0, n - 1, -5) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 1) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 10) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 3) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 7) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 11) << endl;
//cout << BinarySearch02(nums, 0, n - 1, -5) << endl;
cout << BinarySearch03(nums, 0, n - 1, 1) << endl;
cout << BinarySearch03(nums, 0, n - 1, 10) << endl;
cout << BinarySearch03(nums, 0, n - 1, 3) << endl;
cout << BinarySearch03(nums, 0, n - 1, 7) << endl;
cout << BinarySearch03(nums, 0, n - 1, 11) << endl;
cout << BinarySearch03(nums, 0, n - 1, -5) << endl;
return 0;
}
二、例题
1.查询最左边的数
题目:给一段排好序的数,求最左边的数的下标(该数有多个)
int BinarySearch04(int nums[], int left, int right, int key) //通用模板
{
int l = left, r = right;
while (l < r)
{
int mid = l + r >> 1;
if (nums[mid] >= key) r = mid;
else l = mid + 1;
}
if (nums[l] != key) return -1;
return l; //l和r是一样的
}
int main()
{
int nums2[10] = { 0,1,2,3,4,4,4,4,5,6 };
int n2 = sizeof(nums2) / sizeof(nums2[0]);
cout << BinarySearch04(nums2, 0, n2 - 1, 4) << endl; //4
return 0;
}
2.查询最右边的数
题目:给一段排好序的数,求最右边的数的下标(该数有多个)
int BinarySearch05(int nums[], int left, int right, int key) //通用模板
{
int l = left, r = right;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (nums[mid] <= key) l = mid;
else r = mid - 1;
}
if (nums[l] != key) return -1;
return l; //l和r是一样的
}
int main()
{
int nums2[10] = { 0,1,2,3,4,4,4,4,5,6 };
int n2 = sizeof(nums2) / sizeof(nums2[0]);
cout << BinarySearch05(nums2, 0, n2 - 1, 4) << endl; //7
return 0;
}
三、详解二分通用模板
这个二分模板,就是通过砍半和判断条件,把l和r逼到同一个下标,最终这个下标就是想要的结果
int BinarySearch04(int nums[], int left, int right, int key) //通用模板
{
int l = left, r = right;
while (l < r)
{
int mid = l + r >> 1; //这里因为边界问题当下一行是l = mid时就得+1了,详情见第二个例题
if (nums[mid] >= key) r = mid; //这里就是if(满足条件) l = mid 或者 r = mid
else l = mid + 1; //这里要么是l = mid + 1要么就是r = mid - 1就是看上面的情况
}
if (nums[l] != key) return -1;
return l; //l和r是一样的
}
四、附录(所有代码)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<stdlib.h>
#include<stdexcept>
#include<exception>
#include<new>
#include<typeinfo>
#include<fstream>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<unordered_map>
#include<unordered_set>
#include<algorithm>
#include<numeric>
#include<ctype.h>
#include<memory>
#include<functional>
#include<thread>
#include<mutex>
#include<condition_variable>
using namespace std;
int BinarySearch01(int nums[], int left, int right, int key) //非递归写法
{
while (left <= right)
{
int mid = (left + right) / 2;
//int mid = (right - left) / 2 + left;
// int mid = left + right >> 1;
if (nums[mid] > key)
{
right = mid - 1;
}
else if (nums[mid] < key)
{
left = mid + 1;
}
else
{
return mid;
}
}
return -1; //代表无该值
}
int BinarySearch02(int nums[], int left, int right, int key) //递归写法
{
if (left > right) return -1;
int mid = left + right >> 1;
if (nums[mid] > key) return BinarySearch02(nums, left, mid - 1, key);
else if (nums[mid] < key) return BinarySearch02(nums, mid + 1, right, key);
return mid;
}
int BinarySearch03(int nums[], int left, int right, int key) //通用模板
{
int l = left, r = right;
while (l < r)
{
int mid = l + r >> 1;
if (nums[mid] >= key) r = mid;
else l = mid + 1;
}
if (nums[l] != key) return -1;
return l; //l和r是一样的
}
int BinarySearch04(int nums[], int left, int right, int key) //通用模板
{
int l = left, r = right;
while (l < r)
{
int mid = l + r >> 1;
if (nums[mid] >= key) r = mid;
else l = mid + 1;
}
if (nums[l] != key) return -1;
return l; //l和r是一样的
}
int BinarySearch05(int nums[], int left, int right, int key) //通用模板
{
int l = left, r = right;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (nums[mid] <= key) l = mid;
else r = mid - 1;
}
if (nums[l] != key) return -1;
return l; //l和r是一样的
}
int main()
{
int nums[10] = { 1,2,3,4,5,6,7,8,9,10 };
int n = sizeof(nums) / sizeof(nums[0]);
//cout << BinarySearch01(nums, 0, n - 1, 1) << endl;
//cout << BinarySearch01(nums, 0, n - 1, 10) << endl;
//cout << BinarySearch01(nums, 0, n - 1, 3) << endl;
//cout << BinarySearch01(nums, 0, n - 1, 7) << endl;
//cout << BinarySearch01(nums, 0, n - 1, 11) << endl;
//cout << BinarySearch01(nums, 0, n - 1, -5) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 1) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 10) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 3) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 7) << endl;
//cout << BinarySearch02(nums, 0, n - 1, 11) << endl;
//cout << BinarySearch02(nums, 0, n - 1, -5) << endl;
//cout << BinarySearch03(nums, 0, n - 1, 1) << endl;
//cout << BinarySearch03(nums, 0, n - 1, 10) << endl;
//cout << BinarySearch03(nums, 0, n - 1, 3) << endl;
//cout << BinarySearch03(nums, 0, n - 1, 7) << endl;
//cout << BinarySearch03(nums, 0, n - 1, 11) << endl;
//cout << BinarySearch03(nums, 0, n - 1, -5) << endl;
int nums2[10] = { 0,1,2,3,4,4,4,4,5,6 };
int n2 = sizeof(nums2) / sizeof(nums2[0]);
//cout << BinarySearch04(nums2, 0, n2 - 1, 4) << endl; //4
cout << BinarySearch05(nums2, 0, n2 - 1, 4) << endl; //7
return 0;
}
五、扩展题
1.机器人跳跃问题
机器人正在玩一个古老的基于 DOS 的游戏。
游戏中有N+1座建筑——从0到N编号,从左到右排列。编号为 0的建筑高度为 0个单位,编号为 i的建筑高度为 H(i)个单位。
起初,机器人在编号为 0的建筑处。每一步,它跳到下一个(右边)建筑。
假设机器人在第 k个建筑,且它现在的能量值是 E,下一步它将跳到第 k+1个建筑。如果 H(k+1)>E,那么机器人就失去 H(k+1)−E的能量值,
否则它将得到 E−H(k+1)的能量值。
游戏目标是到达第 N个建筑,在这个过程中能量值不能为负数个单位。
现在的问题是机器人至少以多少能量值开始游戏,才可以保证成功完成游戏?
输入格式
第一行输入整数 N,第二行是 N个空格分隔的整数,H(1),H(2),…,H(N)代表建筑物的高度。
输出格式
输出一个整数,表示所需的最少单位的初始能量值上取整后的结果。
数据范围
1≤N,H(i)≤1e5
思路:
当能量为e时,下一个多:E = E - (H[k+1] - E) -> E = 2 * E - H[k+1]
下一个少:E = E + E - H[k+1] -> E = 2 * E - H[k+1]
所以不论下一个多还是少,公式都是一样的。
其次最后的结果满足的话,那么比它大的数肯定也是满足条件的,所以这就跟那个第一个例题一样了,
找最左边的数,只不过要加个check()是否满足条件,然后最小从0开始,最大的值肯定是1e5了,因为
H[i]最大为1e5,那么之后肯定都是加法了,肯定不会做减法,从而也就不会 < 0 了,那么直接看代码
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 1e5 + 10; //防止边界问题
int h[N];
int n;
bool check(int e)
{
for(int i = 1; i <= n; ++i)
{
e = 2 * e - h[i];
if(e >= 1e5) return true; //若当前已经超过了1e5根据之前的思路可以直接返回了
if(e < 0) return false;
}
return true;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &h[i]); //数据量大,所以推荐用scanf读比较快一些
int l = 0, r = 1e5;
while(l < r)
{
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
return 0;
}