二分法

基本思想

算法:当数据量很大适宜采用该方法。采用二分法查找时,数据需是排好序的。
基本思想:假设数据是按升序排序的,对于给定值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;
};

154. 寻找旋转排序数组中的最小值 II

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值