二分查找

二分查找

正常实现

Input : [1,2,3,4,5]

key : 3

return the index : 2

public int binarySearch(int[] nums, int key) {

    int l = 0, h = nums.length - 1;

    while (l <= h) {

        int m = l + (h - l) / 2;

        if (nums[m] == key) {

            return m;

        } else if (nums[m] > key) {

            h = m - 1;

        } else {

            l = m + 1;

        }

    }

    return -1;

}

时间复杂度

二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度为 O(logN)。

m 计算

有两种计算中值 m 的方式:

  • m = (l + h) / 2
  • m = l + (h - l) / 2

l + h 可能出现加法溢出,也就是说加法的结果大于整型能够表示的范围。但是 l 和 h 都为正数,因此 h - l 不会出现加法溢出问题。所以,最好使用第二种计算法方法。

未成功查找的返回值

循环退出时如果仍然没有查找到 key,那么表示查找失败。可以有两种返回值:

  • -1:以一个错误码表示没有查找到 key
  • l:将 key 插入到 nums 中的正确位置

变种

二分查找可以有很多变种,变种实现要注意边界值的判断。例如在一个有重复元素的数组中查找 key 的最左位置的实现如下:

public int binarySearch(int[] nums, int key) {

    int l = 0, h = nums.length - 1;

    while (l < h) {

        int m = l + (h - l) / 2;

        if (nums[m] >= key) {

            h = m;

        } else {

            l = m + 1;

        }

    }

    return l;

}

该实现和正常实现有以下不同:

  • h 的赋值表达式为 h = m
  • 循环条件为 l < h
  • 最后返回 l 而不是 -1

在 nums[m] >= key 的情况下,可以推导出最左 key 位于 [l, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解。

在 h 的赋值表达式为 h = m 的情况下,如果循环条件为 l <= h,那么会出现循环无法退出的情况,因此循环条件只能是 l < h。以下演示了循环条件为 l <= h 时循环无法退出的情况:(所以写while循环条件就根据h的赋值来决定是否需要=号)

nums = {0, 1, 2}, key = 1

l   m   h

0   1   2  nums[m] >= key

0   0   1  nums[m] < key

1   1   1  nums[m] >= key

1   1   1  nums[m] >= key

...

当循环体退出时,不表示没有查找到 key,因此最后返回的结果不应该为 -1。为了验证有没有查找到,需要在调用端判断一下返回位置上的值和 key 是否相等。

1. 求开方

69. Sqrt(x) (Easy)

Input: 4

Output: 2

 

Input: 8

Output: 2

Explanation: The square root of 8 is 2.82842..., and since we want to return an integer, the decimal part will be truncated.

一个数 x 的开方 sqrt 一定在 0 ~ x 之间,并且满足 sqrt == x / sqrt。可以利用二分查找在 0 ~ x 之间查找 sqrt。

对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。

public int mySqrt(int x) {

    if (x <= 1) {

        return x;

    }

    int l = 1, h = x;

    while (l <= h) {

        int mid = l + (h - l) / 2;

        int sqrt = x / mid;

        if (sqrt == mid) {

            return mid;

        } else if (mid > sqrt) {

            h = mid - 1;

        } else {

            l = mid + 1;

        }

    }

    return h;

}

2. 大于给定元素的最小元素

744. Find Smallest Letter Greater Than Target (Easy)

Input:

letters = ["c", "f", "j"]

target = "d"

Output: "f"

 

Input:

letters = ["c", "f", "j"]

target = "k"

Output: "c"

题目描述:给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符,如果找不到就返回第 1 个字符。

public char nextGreatestLetter(char[] letters, char target) {

    int n = letters.length;

    int l = 0, h = n - 1;

    while (l <= h) {

        int m = l + (h - l) / 2;

        if (letters[m] <= target) {

            l = m + 1;

        } else {

            h = m - 1;

        }

    }

    return l < n ? letters[l] : letters[0];

}

3. 有序数组的 Single Element

540. Single Element in a Sorted Array (Medium)

Input: [1, 1, 2, 3, 3, 4, 4, 8, 8]

Output: 2

题目描述:一个有序数组只有一个数不出现两次,找出这个数。

要求以 O(logN) 时间复杂度进行求解,因此不能遍历数组并进行异或操作来求解,这么做的时间复杂度为 O(N)。

令 index 为 Single Element 在数组中的位置。在 index 之后,数组中原来存在的成对状态被改变。如果 m 为偶数,并且 m + 1 < index,那么 nums[m] == nums[m + 1];m + 1 >= index,那么 nums[m] != nums[m + 1]。

从上面的规律可以知道,如果 nums[m] == nums[m + 1],那么 index 所在的数组位置为 [m + 2, h],此时令 l = m + 2;如果 nums[m] != nums[m + 1],那么 index 所在的数组位置为 [l, m],此时令 h = m。

因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式。

public int singleNonDuplicate(int[] nums) {

    int l = 0, h = nums.length - 1;

    while (l < h) {

        int m = l + (h - l) / 2;

        if (m % 2 == 1) {

            m--;   // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数

        }

        if (nums[m] == nums[m + 1]) {

            l = m + 2;

        } else {

            h = m;

        }

    }

    return nums[l];

}

4. 第一个错误的版本

278. First Bad Version (Easy)

题目描述:给定一个元素 n 代表有 [1, 2, ..., n] 版本,在第 x 位置开始出现错误版本,导致后面的版本都错误。可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。

如果第 m 个版本出错,则表示第一个错误的版本在 [l, m] 之间,令 h = m;否则第一个错误的版本在 [m + 1, h] 之间,令 l = m + 1。

因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。

public int firstBadVersion(int n) {

    int l = 1, h = n;

    while (l < h) {

        int mid = l + (h - l) / 2;

        if (isBadVersion(mid)) {

            h = mid;

        } else {

            l = mid + 1;

        }

    }

    return l;

}

5. 旋转数组的最小数字

153. Find Minimum in Rotated Sorted Array (Medium)

Input: [3,4,5,1,2],

Output: 1

public int findMin(int[] nums) {

    int l = 0, h = nums.length - 1;

    while (l < h) {

        int m = l + (h - l) / 2;

        if (nums[m] <= nums[h]) {

            h = m;

        } else {

            l = m + 1;

        }

    }

    return nums[l];

}

 

5、寻找旋转排序数组中的最小值

 

题目来源:leetcode 153

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

 

你可以假设数组中不存在重复元素。

 

    示例 1:

    输入: [3,4,5,1,2]

    输出: 1

 

    示例 2:

    输入: [4,5,6,7,0,1,2]

    输出: 0

 

解答:两种方法:第一种,遍历找最小值,运行时间为O(n).

这里主要讲一下第二种方法:二分法。

旋转后的数组可以分为两个部分,前一部分、后一部分,两个部分均有序,并且前一部分的第一个元素(最小元素) 大于 后一部分的最后一个元素(最大元素)。

搞清楚以上这一点,接下来就很简单了。首先设置left, rigth两个指针,指向首尾两个元素,如果nums[left] < nums[right],则说明没有旋转,直接返回第一个元素即可。如果大于,则说明发生旋转,令middle = left + (right - left) /2, 并比较nums[middle] 和nums[left]值,如果nums[left] <= nums[middle] 则令 left = middle + 1, 否则令 right = middle, 再比较left和right所指向的元素的值,。。。如此迭代即可。

 

代码:

 

class Solution {

public:

    int findMin(vector& nums) {

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

        if( nums[left] > nums[right]) //发生旋转

            while( left < right){

                int middle = left + (right - left) / 2;

                if( nums[middle] < nums[left])

                    right = middle;

                else if( nums[ middle] >= nums[left])

                    left = middle + 1;

               

                if( nums[left] > nums[right])//依旧存在旋转

                    continue;

                else

                    return nums[left];

                   

            }

        else//没有发生旋转

            left = 0;

        return nums[left];

    }

};

 

 

 

做几点说明r代表旋转点, 第三种情况就是没有旋转。

重点考虑第一二种情况

二分查找_第1张图片

https://github.com/luliyucoordinate/Leetcode/blob/master/src/0153-Find-Minimum-in-Rotated-Sorted-Array/0153.cpp

#include

 

 

#include

 

 

using namespace std;

 

 

static int x = []() {std::ios::sync_with_stdio(false); cin.tie(0); return 0; }();

 

 

class Solution

 

 

{

 

 

public:

 

 

int findMin(vector& nums)

 

 

{

 

 

int low = 0, high = nums.size() - 1;

 

 

while (low <= high)

 

 

{

 

 

int mid = (high - low)/2 + low;

 

 

if (nums[low] <= nums[mid])

 

 

{

 

 

if (nums[mid] <= nums[high]) return nums[low];

 //针对有序情况,避免死循环 0 1 2

else low = mid + 1;

}

else

{

if (nums[mid] <= nums[high]) high = mid;

}

}

return -1;

}

};

int main()

{

vector nums = {0, -2};

cout << Solution().findMin(nums) << endl;

return 0;

}}{}

      }

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

  1. class Solution {  
  2. public:  
  3.     int findMin(vector& nums) {  
  4. int l = 0, h = nums.size() -1;  
  5.         while(l < h)  
  6.         {  
  7. int m = (h - l)/2 + l;  
  8.             if(nums[h] > nums[m])  
  9.             {  
  10.                     h = m;  
  11.             }  
  12.             else if( nums[h] < nums[m])  
  13.             {  
  14.                 l = m + 1;  
  15.             }  
  16.             else  
  17.             {  
  18.               h = h -1;//这个逼近且保证!  
  19.             }  
  20.         }  
  21.         return nums[l];  
  22.     }  

}; 

 

Leetcode 33. 搜索旋转排序数组

class Solution {

public:

    int search(vector& nums, int target) {

int l = 0, h = nums.size() -1;

        while(l <= h)

        {

int m = (h - l)/2 + l;

            if(nums[m] == target )

                return m;

            if(nums[m] >= nums[l])//针对只有一个情况 包含等号

            {   //旋转点在m之后, 只在旋转点前半有序序列找

                if(target <=nums[m] && target >= nums[l])

                    h = m;

                else

                    l = m + 1;

            }

            else

            {   //旋转点在m之前,只在旋转点之后的后半有序序列找

                if(target >=nums[m] && target <= nums[h])

                    l = m + 1;

                else

                    h = m;

            }

               

        }

        return -1;

       

    }

};

  1.  

6. 查找区间

34. Find First and Last Position of Element in Sorted Array

Input: nums = [5,7,7,8,8,10], target = 8

Output: [3,4]

 

Input: nums = [5,7,7,8,8,10], target = 6

Output: [-1,-1]

public int[] searchRange(int[] nums, int target) {

    int first = binarySearch(nums, target);

    int last = binarySearch(nums, target + 1) - 1;

    if (first == nums.length || nums[first] != target) {

        return new int[]{-1, -1};

    } else {

        return new int[]{first, Math.max(first, last)};

    }

}

 

private int binarySearch(int[] nums, int target) {

    int l = 0, h = nums.length; // 注意 h 的初始值

    while (l < h) {

        int m = l + (h - l) / 2;

        if (nums[m] >= target) {

            h = m;

        } else {

            l = m + 1;

        }

    }

    return l;

}

34. 在排序数组中查找元素的第一个和最后一个位置

每次记录的都是最后等于target的值不用去做边界值得判断了。

  1. class Solution {  
  2. public:  
  3.       
  4.     vector searchRange(vector& nums, int target) {  
  5.         vector rs = {-1,-1};  
  6.         if(nums.empty())  
  7.             return rs;   
  8.   
  9.         int l = 0, h = nums.size();  
  10.         while(l < h)  
  11.         {  
  12.             int m = (h-l)/2 + l;  
  13.             if(target == nums[m])  
  14.             {  
  15.                 rs[0] = m;  
  16.                 h = m;  
  17.             }  
  18.             else if(target < nums[m])  
  19.             {  
  20.                 h = m;  
  21.             }  
  22.             else if(target > nums[m])  
  23.             {  
  24.                 l = m + 1;  
  25.             }  
  26.         }  
  27.   
  28.           
  29.         l = 0, h = nums.size() ;  
  30.         while(l < h)  
  31.         {  
  32.             int m = (h-l)/2 + l;  
  33.             if(target == nums[m])  
  34.             {  
  35.                 rs[1] = m;  
  36.                 l = m + 1;  
  37.             }  
  38.             else if(target < nums[m] )  
  39.             {  
  40.                 h = m;  
  41.             }  
  42.             else if(target > nums[m])  
  43.             {  
  44.                 l = m + 1;  
  45.             }  
  46.         }  
  47.         return rs;  
  48.     }  
  49.       
  50.   
  51. };  

 

二分查找的思路

我相信对很多读者朋友来说,编写二分查找的算法代码属于玄学编程,虽然看起来很简单,就是会出错,要么会漏个等号,要么少加个 1。

 

不要气馁,因为二分查找其实并不简单。看看 Knuth 大佬(发明 KMP 算法的那位)怎么说的:

 

Although the basic idea of binary search is comparatively straightforward,

the details can be surprisingly tricky...

 

这句话可以这样理解:思路很简单,细节是魔鬼。

 

本文以问答的形式,探究几个最常用的二分查找场景:寻找一个数、寻找左侧边界、寻找右侧边界。第一个场景是最简单的算法形式,解决 这道题,后两个场景就是本题。

 

而且,我们就是要深入细节,比如不等号是否应该带等号,mid 是否应该加一等等。分析这些细节的差异以及出现这些差异的原因,保证你能灵活准确地写出正确的二分查找算法。

零、二分查找框架

 

int binarySearch(int[] nums, int target) {

    int left = 0, right = ...;

 

    while(...) {

        int mid = (right + left) / 2;

        if (nums[mid] == target) {

            ...

        } else if (nums[mid] < target) {

            left = ...

        } else if (nums[mid] > target) {

            right = ...

        }

    }

    return ...;

}

 

分析二分查找的一个技巧是:不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。本文都会使用 else if,旨在讲清楚,读者理解后可自行简化。

 

其中 ... 标记的部分,就是可能出现细节问题的地方,当你见到一个二分查找的代码时,首先注意这几个地方。后文用实例分析这些地方能有什么样的变化。

 

另外声明一下,计算 mid 时需要技巧防止溢出,即 mid=left+(right-left)/2。本文暂时忽略这个问题。

一、寻找一个数(基本的二分搜索)

 

这个场景是最简单的,肯能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。

 

int binarySearch(int[] nums, int target) {

    int left = 0;

    int right = nums.length - 1; // 注意

 

    while(left <= right) {

        int mid = (right + left) / 2;

        if(nums[mid] == target)

            return mid;

        else if (nums[mid] < target)

            left = mid + 1; // 注意

        else if (nums[mid] > target)

            right = mid - 1; // 注意

        }

    return -1;

}

 

1. 为什么 while 循环的条件中是 <=,而不是 < ?

 

答:因为初始化 right 的赋值是 nums.length-1,即最后一个元素的索引,而不是 nums.length。

 

这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。

 

我们这个算法中使用的是前者 [left, right] 两端都闭的区间。这个区间其实就是每次进行搜索的区间,我们不妨称为「搜索区间」。

 

什么时候应该停止搜索呢?当然,找到了目标值的时候可以终止:

 

    if(nums[mid] == target)

        return mid;

 

但如果没找到,就需要 while 循环终止,然后返回 -1。那 while 循环什么时候应该终止?搜索区间为空的时候应该终止,意味着你没得找了,就等于没找到嘛。

 

while(left <= right) 的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2],可见这时候搜索区间为空,因为没有数字既大于等于 333 又小于等于 222 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。

 

while(left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候搜索区间非空,还有一个数 222,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 222 没有被搜索,如果这时候直接返回 -1 就是错误的。

 

当然,如果你非要用 while(left < right) 也可以,我们已经知道了出错的原因,就打个补丁好了:

 

//...

while(left < right) {

    // ...

}

return nums[left] == target ? left : -1;

 

2. 为什么 left = mid + 1,right = mid - 1?我看有的代码是 right = mid 或者 left = mid,没有这些加加减减,到底怎么回事,怎么判断?

 

答:这也是二分查找的一个难点,不过只要你能理解前面的内容,就能够很容易判断。

 

刚才明确了「搜索区间」这个概念,而且本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,如何确定下一步的搜索区间呢?

 

当然是 [left, mid - 1] 或者 [mid + 1, right] 对不对?因为 mid 已经搜索过,应该从搜索区间中去除。

 

3. 此算法有什么缺陷?

 

答:至此,你应该已经掌握了该算法的所有细节,以及这样处理的原因。但是,这个算法存在局限性。

 

比如说给你有序数组 nums = [1,2,2,2,3],target = 2,此算法返回的索引是 222,没错。但是如果我想得到 target 的左侧边界,即索引 111,或者我想得到 target 的右侧边界,即索引 333,这样的话此算法是无法处理的。

 

这样的需求很常见。你也许会说,找到一个 target,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的复杂度了。

 

我们后续的算法就来讨论这两种二分查找的算法。

二、寻找左侧边界的二分搜索

 

直接看代码,其中的标记是需要注意的细节:

 

int left_bound(int[] nums, int target) {

    if (nums.length == 0) return -1;

    int left = 0;

    int right = nums.length; // 注意

   

    while (left < right) { // 注意

        int mid = (left + right) / 2;

        if (nums[mid] == target) {

            right = mid;

        } else if (nums[mid] < target) {

            left = mid + 1;

        } else if (nums[mid] > target) {

            right = mid; // 注意

        }

    }

    return left;

}

 

1. 为什么 while(left < right) 而不是 <= ?

 

答:用相同的方法分析,因为 right = nums.length 而不是 nums.length - 1 。因此每次循环的「搜索区间」是 [left, right) 左闭右开。

 

while(left < right) 终止的条件是 left == right,此时搜索区间 [left, left) 为空,所以可以正确终止。

 

2. 为什么没有返回 -1 的操作?如果 nums 中不存在 target 这个值,怎么办?

 

答:因为要一步一步来,先理解一下这个「左侧边界」有什么特殊含义:

 

binarySearch

 

对于这个数组,算法会返回 111。这个 111 的含义可以这样解读:nums 中小于 222 的元素有 111 个。

 

比如对于有序数组 nums = [2,3,5,7], target = 1,算法会返回 000,含义是:nums 中小于 111 的元素有 000 个。

 

再比如说 nums 不变,target = 8,算法会返回 444,含义是:nums 中小于 888 的元素有 444 个。

 

综上可以看出,函数的返回值(即 left 变量的值)取值区间是闭区间 [0, nums.length],所以我们简单添加两行代码就能在正确的时候 return -1:

 

while (left < right) {

    //...

}

// target 比所有数都大

if (left == nums.length) return -1;

// 类似之前算法的处理方式

return nums[left] == target ? left : -1;

 

3. 为什么 left = mid + 1,right = mid ?和之前的算法不一样?

 

答:这个很好解释,因为我们的「搜索区间」是 [left, right) 左闭右开,所以当 nums[mid] 被检测之后,下一步的搜索区间应该去掉 mid 分割成两个区间,即 [left, mid) 或 [mid + 1, right)。

 

4. 为什么该算法能够搜索左侧边界?

 

答:关键在于对于 nums[mid] == target 这种情况的处理:

 

    if (nums[mid] == target)

        right = mid;

 

可见,找到 target 时不要立即返回,而是缩小「搜索区间」的上界 right,在区间 [left, mid) 中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。

 

5. 为什么返回 left 而不是 right?

 

答:都是一样的,因为 while 终止的条件是 left == right。

三、寻找右侧边界的二分查找

 

寻找右侧边界和寻找左侧边界的代码差不多,只有两处不同,已标注:

 

int right_bound(int[] nums, int target) {

    if (nums.length == 0) return -1;

    int left = 0, right = nums.length;

   

    while (left < right) {

        int mid = (left + right) / 2;

        if (nums[mid] == target) {

            left = mid + 1; // 注意

        } else if (nums[mid] < target) {

            left = mid + 1;

        } else if (nums[mid] > target) {

            right = mid;

        }

    }

    return left - 1; // 注意

}

 

1. 为什么这个算法能够找到右侧边界?

 

答:类似地,关键点还是这里:

 

if (nums[mid] == target) {

    left = mid + 1;

 

当 nums[mid] == target 时,不要立即返回,而是增大「搜索区间」的下界 left,使得区间不断向右收缩,达到锁定右侧边界的目的。

 

2. 为什么最后返回 left - 1 而不像左侧边界的函数,返回 left?而且我觉得这里既然是搜索右侧边界,应该返回 right 才对。

 

答:首先,while 循环的终止条件是 left == right,所以 left 和 right 是一样的,你非要体现右侧的特点,返回 right - 1 好了。

 

至于为什么要减一,这是搜索右侧边界的一个特殊点,关键在这个条件判断:

 

if (nums[mid] == target) {

    left = mid + 1;

    // 这样想: mid = left - 1

 

binarySearch

 

因为我们对 left 的更新必须是 left = mid + 1,就是说 while 循环结束时,nums[left] 一定不等于 target 了,而 nums[left-1] 可能是 target。更多精彩文章欢迎关注我的众公号 labuladong。

 

至于为什么 left 的更新必须是 left = mid + 1,同左侧边界搜索,就不再赘述。

 

3. 为什么没有返回 −1-1−1 的操作?如果 nums 中不存在 target 这个值,怎么办?

 

答:类似之前的左侧边界搜索,因为 while 的终止条件是 left == right,就是说 left 的取值范围是 [0, nums.length],所以可以添加两行代码,正确地返回 −1-1−1:

 

while (left < right) {

    // ...

}

if (left == 0) return -1;

return nums[left-1] == target ? (left-1) : -1;

 

四、最后总结

 

来梳理一下这些细节差异的因果逻辑:

 

第一个,最基本的二分查找算法:

 

因为我们初始化 right = nums.length - 1

所以决定了我们的「搜索区间」是 [left, right]

所以决定了 while (left <= right)

同时也决定了 left = mid+1 和 right = mid-1

 

因为我们只需找到一个 target 的索引即可

所以当 nums[mid] == target 时可以立即返回

 

第二个,寻找左侧边界的二分查找:

 

因为我们初始化 right = nums.length

所以决定了我们的「搜索区间」是 [left, right)

所以决定了 while (left < right)

同时也决定了 left = mid + 1 和 right = mid

 

因为我们需找到 target 的最左侧索引

所以当 nums[mid] == target 时不要立即返回

而要收紧右侧边界以锁定左侧边界

 

第三个,寻找右侧边界的二分查找:

 

因为我们初始化 right = nums.length

所以决定了我们的「搜索区间」是 [left, right)

所以决定了 while (left < right)

同时也决定了 left = mid + 1 和 right = mid

 

因为我们需找到 target 的最右侧索引

所以当 nums[mid] == target 时不要立即返回

而要收紧左侧边界以锁定右侧边界

 

又因为收紧左侧边界时必须 left = mid + 1

所以最后无论返回 left 还是 right,必须减一

 

如果以上内容你都能理解,那么恭喜你,二分查找算法的细节不过如此。

 

通过本文,你学会了:

 

    分析二分查找代码时,不要出现 else,全部展开成 else if 方便理解。

 

    注意「搜索区间」和 while 的终止条件,如果存在漏掉的元素,记得在最后检查。

 

    如需要搜索左右边界,只要在 nums[mid] == target 时做修改即可。搜索右侧时需要减一。

 

以后就算遇到其他的二分查找变形,运用这几点技巧,也能保证你写出正确的代码。

 

最后,点击我的头像可以查看更多详细题解,希望读者多多点赞,让我感受到你的认可~

 

这部分转载公众号 labuladong 致力于把算法问题讲清楚~

 

 

 

你可能感兴趣的:(leetcode,二分查找,LEETCODE,二分查找)