二分法之一发入魂:原理,实现细节,做题思路

一. 摘要

这一周基本都在刷二分法的题,因此在这里做一个总结。二分法是我们耳熟能详的一种算法,其核心思想我认为就是6个字:缩小搜索范围。虽然思想简单,但是在实现的过程中却有很多细节需要注意。本文将对二分算法的原理,实现细节作简要的介绍,然后从leetcode中挑选一道题目来巩固练习。

二. 原理

二分算法的思想很简单,目的是通过缩小搜索范围以达到提高算法效率的目的。以“在数组中搜索一个目标数字”为例:
在这里插入图片描述
算法的步骤为:

  1. 中间的数(1号位置)是不目标值?
  2. 若不是目标值,则缩小目标区间。若该值比目标值大,则新的搜索范围为2号区域,否则为3号区域。、
  3. 不断迭代,直到找到目标值为止。

注意:使用二分法之前一定要注意!二分法只能用于有序数组中,因为他原理上是通过判断中间与目标值之间的某种关系来缩小范围,在关系缺失的前提下,算法即会失效。

三. 实现

1.程序的一般思路

我个人总结了写二分法程序的流程,如下图:
在这里插入图片描述
同样以”寻找数组中的目标值“为例,分析代码:
在这里插入图片描述

2. 细节处理

在写程序的过程中,二分法是很讲究细节的,主要包括以下四个方面。.

a. 左右边界的确定:

搜索区域的双边界一般定义为容器的两端,即:

int left = 0 //左边界
int right = nums.size()-1 //右边界

ps:有的算法喜欢把右边界定义为 nums.size(),如果写这一种做法的话后面循环条件,中心值修改都需要作修改。

b. 循环条件的确定

因为我们在初始化的过程中把左右边界定义为容器的两端,因此循环结束的条件应该是
while(left <= right)

为什么这里的条件是小于等于而不是小于呢? 首先我们需要明白两者的区别:

“小于等于” 对应终止条件是: 当左指针>右指针,例如[4,3]; “小于”
对应的终止条件是:当左指针==右指针 ,例如[4,4];

因此使用循环条件小于时,可能会漏判 [4,4],也就是4这一个元素。
这里我们举一个例子来说明:
在这里插入图片描述
按照第一节的程序运行,流程如下图:在这里插入图片描述假设循环条件为小于,那么实际上就不会有round3了(left==right),因此程序也就出错。

小总结:循环条件的选择应该考虑的标准是“有无漏判元素”,在实际操作时可以把右边界的元素设为目标值,在草稿纸上走一遍,就能搞清楚了。

c. 中心值的更新方式

更新中心值有两种方式:

int mid = (right+left)/2
int mid = left+(right-left)/2

这两种方式的区别在于:当left或right都特别大时,两者求和会溢出,因此第一种方式容易出bug,反之第二种方式则无此风险。

d. 左右区间的更新

左右区间的更新规则应该根据题目要求来具体指定。继续”寻找数组中的目标值“为例,该例子中的左/右边界更新规则为left = mid-1以及right= mid+1。为什么这里不是left = mid或者right = mid呢?愿意有两个:

  • 中间元素已经判断过了,所以可以跳过mid索引的位置
  • 可能会出现死循环,同样以“寻找数组中的目标值”为例:
    在这里插入图片描述
    当目标值在右边界,且更新规则为left = mid时,程序在round3之后left的值就永远等于3,不会更新,于是就无法搜索到右边界的位置。

小总结:确定左右边界的跟新规则应该根据题意确定,但要注意有无漏判,在实操时建议在草稿纸上写一些边界的例子来检查。

四:具体例子

本小节将通过leetcode中的一道二分法题目(力扣744)来进一步巩固上述解题思路。
在这里插入图片描述
理解题意:要求是比目标字母大的第一个字母,那么可以使用二分法不断缩小搜小范围,直至找到最后一个大于目标字母的元素即可。
输入的字符串为:vector<char> letters, 目标字母:target

第一步:确定左右边界

int left = 0;
int right = letters.size()-1;

第二步:确定循环条件

while(left <= right)

第三步:确定左右边界的更新规则:
根据题意,可有以下思路:
letters[mid] > target 则比目标字母大的第一个字母只可能在左边,left = mid-1;否则right = mid+1

第四步:确定返回值
实际上这是寻找左边界的过程,因此可以return letters[left]

综合代码:

 char nextGreatestLetter(vector<char>& letters, char target) {   
      
        int left = 0;
        int right = letters.size()- 1;
        while (left  <= right )
        {
            int mid = (left  + right ) / 2;
            if (target < letters[mid])
                right  = mid - 1;
            else
                left   = mid + 1;
        }
        return letters[l];
    }

五. 总结

二分法的解题思路为常规套路+结合题意,常规的套路四步骤是基本框架,万变不离其宗,基本上大部分可用二分法处理的问题都可以用这一套路解决。但是,在不同的场景下应该要根据题意做适当地调整。

六.其他

本文参考于:here

另外,推荐一个github 刷题指南,上面有关于leetcode的题目进行了归类,大家可以适量食用。

本人也是一名初学者,道行不深,如果上文有错漏,欢迎各位指正。本文纯手码字,喜欢的朋友点个赞哈

发布了16 篇原创文章 · 获赞 4 · 访问量 3898

猜你喜欢

转载自blog.csdn.net/qq_33397016/article/details/105441399