【代码随想录】数组1-二分查找
wbfwonderful Lv3

基本知识

  • 数组元素按照顺序排列
  • 数组元素无法删除,只能替代(填充)
  • python 中,使用 list,代码中 List 是类型注解中的一种表示方式,通常出现在类型提示中。python 3.9 之后可以使用内置的 list 来替代 List

704. 二分查找

题目链接 link

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

两个要点

  • 数组中元素按照顺序排列
  • 无重复的数字(否则元素下标不唯一)
    • 如果有重复的数字,要求返回最小的那个呢?

解法

  • 区间定义:定义 target 所在的区间是左闭右开还是左右全闭
  • 核心:将数组分割为三个部分
    • 左右全闭:[0, middle-1] , middle, [middle + 1, len - 1] 或者
    • 左开右闭:[0, middle) , middle, [middle + 1, len)

左右全闭

  • 区间为 [left, right]:
    • 定义 right 为 len(nums) - 1,因为 right 要有含义
    • 循环条件 while(left <= right) ,因为left == right是有意义的
    • 判断条件,若 middle 大于 target ,则遍历左区间,设置 right = middle - 1,因为此时为右闭区间,不会再访问 middle;同理,若 middle 小于 target,则遍历右边区间,设置 left = middle + 1,同样因为是闭区间,不会再访问 middle。
  • 代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Solution:
    def search(self, nums: List[int], target: int) -> int:
    left = 0
    right = len(nums) - 1
    while left <= right:
    middle = int((left + right) / 2)
    if nums[middle] > target:
    right = middle - 1
    elif nums[middle] < target:
    left = middle + 1
    else:
    return middle
    return -1

左闭右开

  • 区间为 [left, right):

    • 定义 right 为 len(nums),因为不会访问 right,所以设置为区间长度
    • 循环条件 while(left < right) ,因为left == right没有意义(右边为开区间)
    • 判断条件,若 middle 大于 target ,则遍历左区间,设置 right = middle,因为此时为右开区间,设置 right=middle 实际上下一次会访问 middle - 1 。
  • 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Solution:
    def search(self, nums: List[int], target: int) -> int:
    left = 0
    right = len(nums)
    while left < right:
    middle = int((left + right) / 2)
    if nums[middle] > target:
    right = middle
    elif nums[middle] < target:
    left = middle + 1
    else:
    return middle
    return -1

相关题目1:35. 搜索插入位置

题目链接 link

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

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

要点

  • 无重复元素 + 升序排列,考虑二分查找

  • 这道题相较于二分查找,只是多了一个 target 不存在的情况,在这个情况下,一定满足 left == right + 1 (左右都为闭区间的情况)

    • 因为,在循环的最后一轮,满足 left == right,此时 只剩下最后一个数字,middle 也为 left (right)。此时若 middle 数小于 target,则 left + 1,反之则 right - 1。
    • 基于这个原理,若 middle 数小于 target,target 应该放在 middle + 1 上,若 middle 数大于 target,则 target 应该放在 middle 上
    • 对比上述两条,可以发现:
      • middle 数小于 target,left + 1,放在 middle + 1的位置
      • middle 数大于 target, right - 1, middle 不变,放在 middle 的位置
      • middle 的变化和 left 相同,即小于时加1,大于时不变,而最后一轮循环时 middle、left、right 均相同,则最后的结果一定为 left。
  • 代码如下,只需将二分查找的代码的最后一行,改为 return left 即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Solution:
    def search(self, nums: List[int], target: int) -> int:
    left = 0
    right = len(nums) - 1
    while left <= right:
    middle = int((left + right) / 2)
    if nums[middle] > target:
    right = middle - 1
    elif nums[middle] < target:
    left = middle + 1
    else:
    return middle
    return left

相关题目2:34. 在排序数组中查找元素的第一个和最后一个位置

题目链接 link
给定一个按照升序排列的整数数组 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]

思路

  • 相似于二分查找,分别找左边界和右边界,找到一个目标后
    • 若要找左边界,则在找到的目标的左边数组中再次查找,right = middle - 1。即认为当前找到的数比实际的 target 大。
    • 相似的,若要找右边界,则在找到的目标的右边数组中再次查找,left = middle + 1。即认为当前找到的数比实际的 target 小。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      class Solution:
      def searchRange(self, nums: List[int], target: int) -> int:

      leftb = mySearch(nums, target, True)
      rightb = mySearch(nums, target, False)

      def mySearch(nums: List[int], target: int, side: bool):
      left = 0
      right = len(nums) - 1
      res = -1

      while left <= right:
      middle = int((left + right) / 2)
      if nums[middle] > target:
      right = middle - 1
      elif nums[middle] < target:
      left = middle + 1
      else:
      res = middle
      if side:
      # 左边界
      right = middle - 1
      else:
      left = middle + 1
      return res


      return [leftb, rightb]

相关题目3:367. 有效的完全平方数

题目链接 link
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。

思路

  • 二分查找,找到第一个平方不大于 x 的数字。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Solution:
    def mySqrt(self, x: int) -> int:
    left = 0
    right = x - 1
    if x == 0: return 0
    if x == 1: return 1
    while left <= right:
    middle = int((left + right) / 2)
    if middle * middle > x:
    right = middle - 1
    elif middle * middle < x:
    left = middle + 1
    else:
    return middle
    return right