关于二分查找的笔记

简介

做题的过程中,经常要对数组进行二分查找,二分查找主要有两种目的:

  1. 判断数组中是否有某个元素
  2. 判断元素在数组中首次出现的位置

本文列出了上述两种目的的代码,以及一些笔记。

判断存在性

判断存在性的代码比较简单,直接贴出来了:

bool
bin_search(vector<int> nums, int target) {
    int lo = 0;
    int hi = nums.size()-1;

    int mid = (lo+hi)/2;
    while (lo <= hi) {
        if (nums[mid] == target) 
            return true;
        else if (nums[mid] < target) {
            lo = mid+1;
        } else {
            hi = mid-1;
        }

        mid = (lo+hi)/2;
    }

    return false;
}

查找位置

这种类型的二分用于查找:

  1. 如果元素存在,元素的第一次出现位置
  2. 如果元素不存在,元素应该在的位置

看过《算法:第四版》的同学应该知道,这就是书中的rank方法。我之前看过书中的代码,不过“纸上得来终觉浅,绝知此事要躬行”,在我做题的时候,虽然能写出rank的代码来,思路却总是不顺畅,这里错一点那里错一点。今天终于下定决心总结一下问题所在:

  1. 变数太多,例如左端点lo什么时候更新,nums[mid]<target还是nums[mid]<=target?lo怎么更新,是lo=mid还是lo=mid+?最后的结果是lo还是hi,还是要另外判断?这些问题总是缠绕着我本来就不清醒的脑子。
  2. 情况太多

在经过思考之后,我总结出自己的两个解决方法:

  1. 一个问题可能有多种解决方式,二分也有多种实现方法,而且这些方法都是一套一套的,一套方法中的各个小步骤往往是固定搭配着用的。为了解决第一个问题,可以先定下一些不变的量,减少变数。
  2. 首先思考简化情况的方法,如果不行的话,穷尽所有情况。

下面说二分的其他两种实现。

第一种实现

int
find(vector<int> nums, int target) {
    int lo = 0;
    int hi = nums.size()-1;

    // 最终lo会指向这个数应该在的位置
    // 那么lo应该指向的是<target的数,最后mid会等于lo,lo需要越过mid
    int mid = (lo+hi)/2;
    while (lo < hi) {
        if (nums[mid] < target) {
            lo = mid+1;
        } else {
            // nums[mid] >= target
            hi = mid;
        }
        mid = (lo+hi)/2;
    }
    return lo;
}

下面记录一下我的思考过程,不确定是否对别人有帮助。
刚开始时代码模板是:

// 1
int
find(vector<int> nums, int target) {
    int lo = 0;
    int hi = nums.size()-1;
    int mid = (lo+hi)/2;

    while () {
		
        mid = (lo+hi)/2;
    }
    return ?;
}

首先,确定一下变数:最终lo要么指向target出现的第一个位置,要么指向它应该在的位置,于是代码变成了:

// 2
int
find(vector<int> nums, int target) {
    int lo = 0;
    int hi = nums.size()-1;
    int mid = (lo+hi)/2;

    while () {
		
        mid = (lo+hi)/2;
    }
    return lo;
}

现在再确定一下变数:lo指向的数小于target,hi指向的数大于等于target。
这样最终会得到一条界限,于是代码变成:

// 1
int
find(vector<int> nums, int target) {
    int lo = 0;
    int hi = nums.size()-1;
    int mid = (lo+hi)/2;

    while () {
		if (nums[mid] < target) {
			lo = mid; // 由于nums[mid]<target,所以可以保证nums[lo]总是小于target
		} else {
			hi = mid; // 同理,可以保证nums[hi] >= target
		}
        mid = (lo+hi)/2;
    }
    return lo;
}

现在先考虑一下最终的情况:

  1. nums[lo]<target; nums[hi]>=target; lo+1==hi;,这是最希望得到的情况,lo和hi分别在界限两边,此时退出循环对lo+1,然后返回lo即可。
  2. nums[lo]>=target; lo==hi;,有可能整个数组都大于等于target
  3. nums[hi]<target; lo==hi;,整个数组都小于target
    由上述三种情况,可以写出代码:
int
find(vector<int> nums, int target) {
    int lo = 0;
    int hi = nums.size()-1;
    int mid = (lo+hi)/2;

    while (lo != mid) {  // 这个条件可以保证在上面三种情况发生时退出
		if (nums[mid] < target) {
			lo = mid; // 由于nums[mid]<target,所以可以保证nums[lo]总是小于target
		} else {
			hi = mid; // 同理,可以保证nums[hi] >= target
		}
        mid = (lo+hi)/2;
    }
    if (nums[lo]<target) {
    	// 第1,3种情况
    	lo+1;
    }
    return lo; // 隐含第2种情况
}

第二种实现

下面是比较简洁的实现:

int
find(vector<int> nums, int target) {
    int lo = 0;
    int hi = nums.size()-1;

    // 最终lo会指向这个数应该在的位置
    // 那么lo应该指向的是<target的数,lo需要越过mid
    int mid = (lo+hi)/2;
    while (lo < hi) {
        if (nums[mid] < target) {
            lo = mid+1;
        } else {
            // nums[mid] >= target
            hi = mid;
        }
        mid = (lo+hi)/2;
    }
    return lo;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值