为什么你的二分总是写错?— — 强烈建议学习

本文介绍了二分查找算法的基本思想、边界条件的处理、避免死循环的方法以及在不同场景下的应用,包括搜索插入位置、查找元素范围和计算平方根等问题。推荐观看相关视频以增强理解。
摘要由CSDN通过智能技术生成

二分

二分的思路非常直白,将每一次的取值范围都缩减为原来的一半。但是在处理边界的时候却很容易写错,很容易陷入死循环中,对此有人进行了总结,链接:二分查找为什么总是写错?_哔哩哔哩_bilibili,该视频总结的非常好,可以说是一次看懂终生难忘了。在此对该视频中讲解的内容进行总结,以便日后查阅使用(强烈建议去观看该视频!!!)。

算法模板:

int searchInsert(vector<int>& nums, int target) {
    int l = -1, r = nums.size();
    while(l + 1 != r){
        int mid =  l + (r - l) / 2;
        if(condition_1){    // 根据具体情况进行条件按的选择
            l = mid;
        }else r = mid;
    }
    return l or r;    // 根据具体情况进行返回
}

我们定义边界 l = -1r = N,只有这样定义进行二分的时候才能包含数组中的所有下标。

证明可行性

证明是否能遍历到所有索引:

l 最小为 -1r的最小值为1,因为判断条件为l + 1 != r,所以mid能取到的最小的索引值为(-1 + 1) / 2 = 0l最大为N - 1r最大为N,那么mid能取到最大的索引值为(N - 1 + N) / 2 = N - 1,【注意:mid的取值是向下取整的】。

因此能够遍历到所有索引

证明是否会陷入死循环:

  1. 对于r = l + 1,根据题目条件(l + 1 != r),因此会退出循环。
  2. 对于r = l + 2,根据判断条件,要么l = (l + r) / 2 = (l + l + 2) / 2 = l + 1,要么r = (l + r) / 2 = (l + l + 2) / 2 = l + 1,接着就进入了第一种情况。
  3. 对于r = l + 4,根据判断条件,要么l = (l + r) / 2 = (l + l + 4) / 2 = l + 2,要么r = (l + r) / 2 = (l + l + 3) / 2 = l + 2,接着就进入了第二种情况,最后会进入第一种情况。
  4. 对于其他情况,最后都会进入到第一种情况的。

因此不会陷入死循环。

如何使用判断条件?

对于数组:

1234555678
情况判断条件1:condition_1判断条件2:condition_2
最后一个小于5的值的索引nums[ mid ] < 5l
第一个等于5的值的索引nums[ mid ] < 5r
最后一个等于5的值的索引nums[ mid ] <= 5l
第一个大于5的值的索引nums[ mid ] <= 5r

l r不会相等,循环结束l与r之间会形成一个边界,如:对于情况1(最后一个小于5的值的索引),循环结束后l 指向下标3,r指向下标4;

举例:最后一个小于5的值的索引

int searchInsert(vector<int>& nums, int target) {
    int l = -1, r = nums.size();
    while(l + 1 != r){
        int mid =  l + (r - l) / 2;
        if(nums[mid] < 5){    // 根据具体情况进行条件按的选择
            l = mid;
        }else r = mid;
    }
    return l;
}

相关题目:

整数二分例题

35. 搜索插入位置

请添加图片描述

代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        if (nums.empty()) return 0;
        int l = -1, r = nums.size();
        while(l + 1 != r){
            int mid =  l + (r - l) / 2;
            if(nums[mid] <= target){
                l = mid;
            }else r = mid;
        }
        if(l < 0 || nums[l] < target) return r;
        return l;
    }
};
34. 在排序数组中查找元素的第一个和最后一个位置

请添加图片描述

代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> ans;
        // 如果数组为空,就直接返回-1 -1
        if(nums.size() == 0){
            ans.push_back(-1);
            ans.push_back(-1);
            return ans;
        }
        int l = -1, r = nums.size();
        // 寻找左边界
        while(l + 1 != r){
            int mid = (l + r) >> 1;
            if(nums[mid] < target){
                l = mid;
            }else r = mid;
            // 最后需要返回r
        }
        if(r >= nums.size() || nums[r] != target){   // 判断是否越界
            ans.push_back(-1);
            ans.push_back(-1);
            return ans;
        }
        ans.push_back(r);
        // 寻找右边界  ——> 需要向上取整
        l = -1, r = nums.size();
        while(l + 1 != r){
            int mid = (l + r) >> 1;
            if(nums[mid] <= target){
                l = mid;    // 最后返回l
            }else{
                r = mid;
            }
        }
        ans.push_back(l);
        return ans;
    }
};
P2249 【深基13.例1】查找 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

请添加图片描述

代码
#include<iostream>
#include<vector>
using namespace std;
int main(){
    int n, m;
    cin>>n>>m;
    vector<int> A(n + 1, 0);
    int maxA = 0;
    for(int i = 1;i < A.size(); i++){
    	cin>>A[i];
    }
    //  进行m次询问 ,第一次出现的编号 
    while(m--){
    	int target; cin>>target;
    	int l = 0, r = A.size();
    	while(l + 1 != r){
    		int mid = (l + r) >> 1;
    		if(A[mid] < target){
    			l = mid;
    		}else r = mid;
    	}
    	if(A[r] != target) r = -1;
    	cout<<r<<' ';
    }
}

实数二分例题

69. x 的平方根

请添加图片描述

代码
class Solution {
public:
    int mySqrt(int x) {
        const double ACC = 1e-5;
        double l = -1, r = x;
        while(r - l > ACC){   // 判断精度,这里精确到小数点5位
            double mid = (l + r) / 2;
            if(mid * mid < x){
                l = mid;
            }else r = mid;    // 因为是向下取整的,所以返回更大的那一个,如果返回小的由于精度的问题,会向下取整到一个错误的答案
        }
        return int(r);
    }
};

注意这里必须使用高精度变量类型double,否则会因为精度不够而陷入死循环!!!

  • 37
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值