二分查找算法 四种题型六道题目总结,从此二分不迷路!

简介: 二分查找算法 四种题型六道题目总结,从此二分不迷路!

前言


二分查找在算法中一般有四类题目:

  1. 排序或通过排序后的数组,快速求某个值的下标
  1. 求某个值在数组中的左右端点
  1. 通过条件判断进行二分查找
  1. 局部有序的二分查找

今天将这四类列举六道题,让大家一次看个够,从此二分不迷路!


35.搜索插入位置


https://leetcode-cn.com/problems/search-insert-position/solution/35sou-suo-cha-ru-wei-zhi-pythonji-chu-er-c0xq/

难度:简单


题目

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,

返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。


示例

示例 1:
输入: [1,3,5,6], 5
输出: 
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0


分析



手写二分解题

class Solution:
    def searchInsert(self, nums, target):
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid
            if nums[mid] > target:
                right = mid - 1
            else:
                left = mid + 1
        return left


api解题

class Solution:
    def searchInsert(self, nums, target):
        return bisect.bisect_left(nums, target)


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


https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/34zai-pai-xu-shu-zu-zhong-cha-zhao-yuan-zoga7/

难度:中等


题目

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

  • 你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?


示例

示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]


分析

二分查找左右端点有一个小tips

  1. left = bisect_left(target),如果left== length or nums[left] != target,
    表示没有找到该数字,返回[-1, -1]
  2. 在基于left的前提下,我们bisect_left(target + 1),
    即可获取target下一个数字的插入点,然后right -1就是结果了。


解题

import bisect
class Solution:
    def searchRange(self, nums, target):
        left = bisect.bisect_left(nums,target)
        if left == len(nums) or nums[left] != target:
            return [-1,-1]
        right = bisect.bisect_left(nums,target+1)
        return [left,right - 1]


278.第一个错误的版本


https://leetcode-cn.com/problems/first-bad-version/solution/278di-yi-ge-cuo-wu-de-ban-ben-by-qingfen-gp99/

难度:简单


题目:

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。


示例:

给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。


分析

这是一道标准的,读题3分钟解题30秒题目。

其实只需要关注查找、尽可能少的次数这些关键字就可以判断出是一道二分查找的题目了。

题目中虚拟构建了 isBadVersion 方法用于判断结果是True or False。

所以,今天只能说是力扣庆祝端午快乐的一道放水送分题...


解题:

class Solution:
    def firstBadVersion(self, n):
        left = 1
        right = n
        while left < right:
            mid = (left + right) // 2
            if isBadVersion(mid):
                right = mid
            else:
                left = mid + 1
        return left


875.爱吃香蕉的珂珂


https://leetcode-cn.com/problems/koko-eating-bananas/solution/875-ai-chi-xiang-jiao-de-ke-ke-er-fen-ch-7you/

难度:中等


题目:

珂珂喜欢吃香蕉。这里有N堆香蕉,第 i 堆中有piles[i]根香蕉。警卫已经离开了,将在H小时后回来。

珂珂可以决定她吃香蕉的速度K(单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。

珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。


示例:

示例 1:

输入: piles = [3,6,7,11], H = 8

输出: 4

示例2:

输入: piles = [30,11,23,4,20], H = 5

输出: 30

示例3:

输入: piles = [30,11,23,4,20], H = 6

输出: 23


分析:

由于1 <= piles.length <= 10^4, piles.length <= H <= 10^9所以肯定是二分查找没跑了。

下来看到最小时间,肯定是二分查找左边界。至于如何选择最大值,题目中描述:

“如果香蕉小于K根,他讲吃掉这堆的所有,并一小时内不会再吃更多香蕉”

所以我们最大值选择这个列表的max值即可。


解题:

class Solution:
    def minEatingSpeed(self, piles, h):
        def cost(k):
            t = 0
            for i in piles:
                t += (i + k - 1) // k
            return t
        left, right = 1, max(piles)
        while left < right:
            mid = (left + right) // 2
            ret = cost(mid)
            if ret <= h:
                right = mid
            else:
                left = mid + 1
        return left


33.搜索旋转排序数组


https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/33sou-suo-xuan-zhuan-pai-xu-shu-zu-pytho-2oia/

难度:中等


题目

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,

使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]

(下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,

则返回它的下标,否则返回 -1 。

提示:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • nums 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -10^4 <= target <= 10^4

进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗?


示例

示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1


分析

第一次看这个题的时候,这能算中等?循环判断在不在不就完了。暴力通过,咦,效率还挺高?

沾沾自喜中看到了进阶要求,使用时间复杂度为O(log n)的方法完成...

O(log n)无疑二分才能实现,但是数组时无序的啊,我们先排序再二分?别逗,排序的时间复杂度就是O(n).

那么如何对局部有序的数组进行二分搜索呢?让我们以示例1为例:

nums = [4,5,6,7,0,1,2], target = 0

设置左右端点,left = 0 right = len(nums) -1。

有的小伙伴们要问了,你求length不是要一个一个加起来,复杂度不是O(n)么,错!

一定要记得Python的len()方法在获取数组长度时的时间复杂度是O(1).至于为啥篇幅原因百度吧...

mid = (0 + 6) // 2 == 3,此时nums[mid] == 7,下面开始判断。

  1. nums[mid]是否等于target,等于返回mid
  2. 关键来了,nums[mid]是否大于nums[left]
  • 如果nums[left] <= nums[mid]
  • nums[left] <= target < nums[mid],此时我们缩减right = mid - 1
  • 否则,缩减left = mid + 1
  • 如果nums[left] > nums[mid]
  • nums[mid] < target <= nums[right],此时我们缩减left += 1
  • 否则,缩减right = mid - 1
    我们通过将局部有序的数据进行分场景考虑的情况,完成了二分的实现,这就是局部有序数组的二分操作方式。


暴力解法

class Solution:
    def search(self, nums, target):
        for i, num in enumerate(nums):
            if target == num:
                return i
        return -1


解题

class Solution:
    def search(self, nums, target):
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid
            elif nums[left] <= nums[mid]:
                if nums[left] <= target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                if nums[mid] < target <= nums[right]:
                    left += 1
                else:
                    right -= 1
        return -1


81.搜索旋转排序数组II


https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/solution/81sou-suo-xuan-zhuan-pai-xu-shu-zu-ii-33-7lz5/

难度:中等


题目

已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,

使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]

(下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。

如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。

提示:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -10^4 <= target <= 10^4

进阶:

  • 这是 搜索旋转排序数组 的延伸题目,本题中的 nums 可能包含重复元素。
  • 这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?


示例

示例 1:
输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true
示例 2:
输入:nums = [2,5,6,0,0,1,2], target = 3
输出:false


分析

先回答进阶问题,nums 可能包含重复元素,这会影响到程序的时间复杂度吗?

两种答案:

  1. 不会,我就是暴力return target in nums 管你有没有重复值呢,哈哈。
  2. 会,使用二分查找局部有序时,当nums[mid] == nums[left]时,
    我无法知道他到底在翻转前的数组还是反转后的数组。只能left左移一位继续判断。

好了,做这道题之前,强烈建议先看看它的基础版题目,然后再来做这道题:

其实这道题,我们只需要根据33题的搜索改变一点判断就行,即当nums[left] == nums[mid] == nums[right],

此时我们不知道该怎么移动,那就left += 1 right += 1,再去判断即可。


解题

class Solution:
    def search(self, nums, target):
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return True
            if nums[left] == nums[mid] == nums[right]:
                left += 1
                right -= 1
                continue
            if nums[mid] <= nums[right]:
                if nums[mid] < target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
            else:
                if nums[left] <= target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
        return False




相关文章
|
2月前
|
存储 算法 JavaScript
怎么刷算法,leetcode上有哪些经典题目
怎么刷算法,leetcode上有哪些经典题目
16 0
|
2月前
|
存储 算法 索引
【优选算法】—— 二分查找
【优选算法】—— 二分查找
|
2月前
|
算法
【算法】——动态规划题目讲解
【算法】——动态规划题目讲解
|
2月前
|
算法 程序员 数据处理
算法与人生 揭秘C语言中高效搜索的秘诀——二分查找算法详解
算法与人生 揭秘C语言中高效搜索的秘诀——二分查找算法详解
|
3月前
|
人工智能 算法 测试技术
【动态规划】【二分查找】C++算法 466 统计重复个数
【动态规划】【二分查找】C++算法 466 统计重复个数
|
4月前
|
算法 测试技术 C#
【KMP】【二分查找】【C++算法】100207. 找出数组中的美丽下标 II
【KMP】【二分查找】【C++算法】100207. 找出数组中的美丽下标 II
|
4月前
|
存储 算法 Java
【算法系列篇】二分查找——这还是你所知道的二分查找算法吗?
【算法系列篇】二分查找——这还是你所知道的二分查找算法吗?
|
24天前
|
算法
算法系列--动态规划--背包问题(4)--完全背包拓展题目(上)
算法系列--动态规划--背包问题(4)--完全背包拓展题目(上)
20 0
|
26天前
|
算法 测试技术 Serverless
【二分查找】【C++算法】378. 有序矩阵中第 K 小的元素
【二分查找】【C++算法】378. 有序矩阵中第 K 小的元素
|
2月前
|
算法 索引
算法思想总结:二分查找算法
算法思想总结:二分查找算法