基本思想
算法:当数据量很大适宜采用该方法。采用二分法查找时,数据需是排好序的。
基本思想:假设数据是按升序排序的,对于给定值key,从序列的中间位置k开始比较,
如果当前位置arr[k]值等于key,则查找成功;
若key小于当前位置值arr[k],则在数列的前半段中查找,arr[low,mid-1];
若key大于当前位置值arr[k],则在数列的后半段中继续查找arr[mid+1,high],
直到找到为止,时间复杂度:O(log(n))
另外我们需特别注意边界
标准二分查找模板
int binarySearch(int[] nums, int target) {
int left = 0, right = n-1;
while(l<=r) {
int mid = left+((right-left)>>1);//此处 如果是left + right 可能溢出。
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = mid+1;
} else if (nums[mid] > target) {
right = mid-1;
}
}
return ...;
}
left + ((right -left) >> 1) 对于目标区域长度为奇数而言,是处于正中间的,对于长度为偶数而言,是中间偏左的。
案例
吃瓜群众
题目描述
某地总共有 M 堆瓜,第 i 堆瓜的数量为 Xi。现有 N 组群众现在想要吃瓜,第 i 组群众想要吃的瓜的数量为 Yi。现在对于每组想吃瓜的群众,需要在 M 堆瓜中查找对应数量的一堆瓜,并输出那堆瓜的编号,若找不到对应数量的一堆,则输出 0。
输入
输入共 3 行。
第一行两个整数 M,N。
第二行 M 个整数分别表示 X1,X2…XM。(保证各不相同)
第三行 N 个整数分别表示 Y1,Y2…YN。(保证各不相同)
输出
对于每个 Yi 输出一行一个整数为对应数量的瓜的编号,若没有对应数量的瓜,则输出 0。
样例输入
5 3
1 3 26 7 15
26 99 3
样例输出
3
0
2
数据规模与约定
时间限制:1 s
内存限制:256 M
100% 的数据保证 ,
/*************************************************************************
> File Name: 386.cpp
> Author: liuchenxu
> Mail: 1154106923@qq.com
> Created Time: 2020年04月10日 星期五 15时17分25秒
************************************************************************/
#include <iostream>
#include <algorithm>
using namespace std;
int m,n;
int y[100005];
struct gua{
int num; //数目
int w; //编号
};
struct gua x[100005];
int binary_sort(int t){
int l = 0,r = m - 1;
while(l <= r){
int mid = (r + l)/2;
if(x[mid].num == y[t]){
return x[mid].w;
}else if(x[mid].num < y[t]){
l = mid + 1;
}else {class Solution {
public:
int findMin(vector<int>& nums) {
int l = 0, r = nums.size() - 1;
if(nums[0] > nums[r]){
while(l != r){
int mid = l + ((r - l) >> 1);
if(nums[mid] < nums[0]){
r = mid;
}else{
l = mid + 1;
}
}
}else{
while(l != r){
int mid = l + ((r - l) >> 1);
if(nums[mid] < nums[r]){
r = mid;
}else{
l = mid + 1;
}
}
}
return nums[l];
}
};
r = mid - 1;
}
}
return 0;
}
bool cmp(gua a, gua b){
return a.num < b.num;
}
int main(){
cin >> m >> n;
for(int i = 0; i < m; i++) {
cin >> x[i].num;
x[i].w = i + 1;
}
sort(x, x + m,cmp);//排序左闭右开
for(int i = 0; i < n; i++){
cin >> y[i];
}
for(int i = 0; i < n; i++){
int ans = binary_sort(i);
cout << ans << endl;
}
return 0;
}
分析:此题是标准的二分查找模板题,此题需要注意的是每次查找判断后要mid+1.
二分查找左边界模板
int binarySearch(int[] nums, int target) {
int left = 0, right = n-1;
while(l!=r) {
int mid = left+((right-left)>>1);
if (nums[mid] < target) {
left = mid+1;
} else if (nums[mid] >= target) {
right = mid;
}
}
return ...;
}
与标准的二分查找不同:
首先,这里的右边界的更新是right = mid。
其次,这里的循环条件是left < right。
因为在最后left与right相邻的时候,mid和left处于相同的位置,则下一步,无论怎样,left, mid, right都将指向同一个位置,如果此时循环的条件是left <= right,则我们需要再进入一遍循环,此时,如果x[mid].num < y[t]还好说,循环正常终止;否则,我们会令right = mid,这样并没有改变left,mid,right的位置,将进入死循环。
下面是寻找左侧边界的二分查找:查找00000111111中的一个1的问题
吃瓜群众升级版
题目描述
某地总共有 M 堆瓜,第 i 堆瓜的数量为 Xi。现有 N 组群众现在想要吃瓜,第 i 组群众想要吃的瓜的数量为 Yi。现在对于每组想吃瓜的群众,需要在 M 堆瓜中查找大于等于需要数量的第一堆瓜,并输出那堆瓜的编号,若所有瓜堆的数量均小于需要数量,则输出 0。
输入
输入共 3行。
第一行两个整数 M,N。
第二行 M 个整数分别表示 X1,X2…XM。(保证各不相同)
第三行 N 个整数分别表示 Y1,Y2…YN。(保证各不相同)
输出
对于每个 Yi 输出一行一个整数为大于等于需要数量的第一堆瓜的编号,若所有瓜堆的数量均小于需要数量,则输出 0。
样例输入
5 5
1 3 26 7 15
27 10 3 4 2
样例输出
0
5
2
4
2
数据规模与约定
时间限制:1 s
内存限制:256 M
100% 的数据保证 1≤M,N≤100,000,1≤Xi,Yi≤1,000,000,000
#include <iostream>
#include <algorithm>
using namespace std;
int m,n;
int y[100005];
struct gua{
int num; //数目
int w; //编号
};
struct gua x[100005];
int binary_sort(int t){
int l = 0,r = m - 1;
if(y[t] > x[m-1].num){
return 0;
}
while(l != r){//注意
int mid = l + (r-l)>>1;//防止溢出
if(x[mid].num < y[t]){
l = mid + 1; //注意
}else if(x[mid].num >= y[t]){
r = mid; //注意
}
}
return x[l].w;
}
bool cmp(gua a, gua b){
return a.num < b.num;
}
int main(){
cin >> m >> n;
for(int i = 0; i < m; i++) {
cin >> x[i].num;
x[i].w = i + 1;
}
sort(x, x + m,cmp);
for(int i = 0; i < n; i++){
cin >> y[i];
}
for(int i = 0; i < n; i++){
int ans = binary_sort(i);
cout << ans << endl;
}
return 0;
}
二分查找右边界模板
int binarySearch(int[] nums, int target) {
int left = 0, right = n-1;
while(l!=r) {
int mid = left+((right-left)>>1+1) ;
if (nums[mid] <= target) {
left = mid;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
return ...;
}
这里大部分和寻找左边界是对称着来写的,唯独有一点需要尤其注意——中间位置的计算变了,我们在末尾多加了1。这样,无论对于奇数还是偶数,这个中间的位置都是偏右的。
对于这个操作的理解,从对称的角度看,寻找左边界的时候,中间位置是偏左的,那寻找右边界的时候,中间位置就应该偏右呗,但是这显然不是根本原因。根本原因是,在最后left和right相邻时,如果mid偏左,则left, mid指向同一个位置,right指向它们的下一个位置,在nums[left]已经等于目标值的情况下,这三个位置的值都不会更新,从而进入了死循环。所以我们应该让mid偏右,这样left就能向右移动。这也就是为什么我们之前一直强调查找条件,判断条件和左右边界的更新方式三者之间需要配合使用。
下面是寻找右侧边界的二分搜索:1111110000中的最后一个1的问题
Leetcode 34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> vec;
if(nums.size()!= 0){
int l = 0,r = nums.size() - 1,mid;
while(l != r){
mid = l + ((r-l)>>1);
if(nums[mid] < target){
l = mid + 1;
}else if(nums[mid] >= target){
r = mid;
}
}
if(nums[l] == target){
vec.push_back(l);
}else{
vec.push_back(-1);
}
l = 0,r = nums.size() - 1;
while(l != r){
mid = l + ((r-l)>>1+1) ;
if(nums[mid] <= target){
l = mid;
}else if(nums[mid] > target){
r = mid - 1;
}
}
if(nums[l] == target){
vec.push_back(l);
}else{
vec.push_back(-1);
}
}else{
vec.push_back(-1);
vec.push_back(-1);
}
return vec;
}
};
以上情况是没有重复元素的情况
Leetcode167. 两数之和 II - 输入有序数组
class Solution {
public:
int binary_search(vector<int>&numbers,int target){
int l = 0,r = numbers.size()-1,mid;
while(l < r){
int mid = l + ((r - l + 1)>>1);
if(numbers[mid] < target){
l = mid;
}else if(numbers[mid] > target){
r = mid -1;
}else{
l++; //左收缩只能采用比较保守的方式
}
}
if(target == numbers[l]) return l+1;
return -1;
}
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> vec;
for(int i = 0;i < numbers.size();i++){
if(binary_search(numbers,target - numbers[i]) > 0){
vec.push_back(i+1);
vec.push_back(binary_search(numbers,target - numbers[i]));
break;
}
}
return vec;
}
//return vec;
};
class Solution {
public:
int minArray(vector<int>& numbers) {
if(numbers.empty()){
return 0;
}
if(numbers.size() == 1){
return numbers[0];
}
int target = numbers[0];
int l = 0, r = numbers.size()-1;
while(l != r){
int mid = l + ((r - l) >> 1);
if(numbers[mid] > numbers[r]){
l = mid + 1;
}else if(numbers[mid] < numbers[r]){
r = mid;
}else{
r--;
}
}
if(numbers[l] < target){
return numbers[l];
}else{
return numbers[0];
}
/* if(numbers.empty()){
return 0;
}
int ans = numbers[0];
for(int i = 0; i < numbers.size(); i++){
if(ans > numbers[i]){
ans = numbers[i];
}
}
return ans;*/
}
};
特殊用法:杨氏矩阵
杨氏矩阵
#include <stdio.h>
#include <stdlib.h>
int find_target(int ** matrix, int n, int m, int target) {
int l = 0,r = m - 1;
while(l < n && r >= 0){
if(matrix[l][r] < target){
l++;
}else if(matrix[l][r] > target){
r--;
}else{
return 1;
}
}
return 0;
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
int ** v;
v = (int**)malloc(n * sizeof(int*));
for (int i = 0; i < m; ++i) {
v[i] = (int*)malloc(m * sizeof(int));
}
int tp;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j){
scanf("%d", &v[i][j]);
}
}
int tot;
scanf("%d", &tot);
for (int i = 0; i < tot; ++i) {
scanf("%d", &tp);
if (find_target(v, n, m, tp)) {
printf("Yes\n");
} else {
printf("No\n");
}
}
return 0;
}
参考:https://segmentfault.com/a/1190000016825704