搜索插入位置,二分查找就够了

496 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情


大家好呀,我是帅蛋。

今天解决搜索插入位置,同样作为二分查找的经典题。

这道题简单,但是不容易 AC,LeetCode 上的通过率很低,不足五成。

这就是之前我在【二分查找】文章里讲的,越简单的东西,越在细节上容易栽跟头。

废话不多说,下面就让我们来切了这道题。

35-0

LeetCode 35:搜索插入位置

题意

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

如果目标值不在数组中,返回它将会被按顺序插入的位置。

示例

输入: nums = [1,3,5,6], target = 5

输出: 2

输入: nums = [1,3,5,6], target = 0

输出: 0

提示

必须使用时间复杂度为 O(logn) 的算法

  • 1 <= len(nums) <= 10^4
  • -10^4 <= nums[i]、target <= 10^4
  • nums 为无重复元素的升序排列数组

题目解析

首先看题意,数组 nums 是无重复元素的升序排列数组,而且是必须使用 O(logn) 时间复杂度的算法。

说实话,当时看到这的时候我愣了一下,好家伙,直接报二分查找身份证号得了。

35-1

知道是二分查找,剩下就是走流程的事,但是这个和平常做的的二分查找还稍微有点不一样。

普通的二分查找的条件是:数组有序、无重复、能找到。

这里要二分查找的条件只是:数组有序、无重复,要找的 target 不一定在数组 nums 中。

target 在 nums 中没啥好说的,target 不在 nums 中,无非就存在 3 种情况

  • target < nums[0],插在数组最前面
  • target 插在某个数组元素后面
  • target > nums[n-1],插在数组最后面

基于这个情况,这道题的解题步骤如下:

  • 初始化左右指针 low 和 high
  • 每次计算中间指针 mid:若 num[mid] == target,直接返回 mid,若 nums[mid] < target,在右区间找,若 nums[mid] > target,在左区间找
  • 查找结束,若没有相等的值,则返回 high + 1,此处为插入位置。

说一下最后一步“若没有相等的值,则返回 high + 1”:

对于二分查找,我的习惯查找的是[low, high]区间,区间是左闭右闭区间(当然你也可以是左闭右开区间)。

此时的 while 循环的条件是 low <= high,那循环终止条件相当于是 low = high + 1,此时区间是 [high + 1, high]。

循环终止代表 target 不在 nums 中,那它就要插入,插入的位置是 high 的右侧,即 high + 1(当然也可以是 left,毕竟此时 left 是等于 high + 1 的)。

如果还不理解的话找几个例子在纸上画一下,秒懂。

35-2

图解

以 nums = [1,3,5,6], target = 0 为例。

首先初始化 low 和 high 指针。

35-3

low, high = 0, len(nums) - 1

第 1 步,low = 0,high = 3,mid = (low + high) // 2 = 1:

35-4

mid = (low + high) // 2

此时 nums[mid] = 3 > target,所以 high 向左移动至 mid - 1 = 0 处。

35-5

if nums[mid] > target:
    high = mid - 1

第 2 步,low = 0,high = 0,mid = (low + high) // 2 = 0:

35-6

此时 nums[mid] = 1 > target,所以 high 向左移动至 mid - 1 = -1 处。

35-7

第 3 步,low = 0,high = -1,此时 high < low,while 循环中止。

此时没有在数组中找到 target,证明 target 小于数组中最小的元素,所以插在下标为 -1 的位置,成为新的下标 0,即 high + 1。

35-8

本题解使用二分查找,时间复杂度为 O(logn)

只额外定义了几个指针,空间复杂度为 O(1)

代码实现

Python 代码实现

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
​
​
        low, high = 0, len(nums) - 1
​
        # 在 [low, high] 找 target
        while low <= high:
​
            mid = (low + high) // 2
​
            # 如果 target 找到,直接返回位置
            if nums[mid] == target:
                return mid
            # 如果 target 大于中间数,则 target 可能在右区间
            # 在 [mid + 1, left] 中找
            elif nums[mid] < target:
                low = mid + 1
            # 如果 target 小于中间数,则 target 可能在左区间
            # 在 [left, mid -1] 中找
            else:
                high = mid - 1
        
        # 如果在数组中没找到,则返回需要插入数值的位置
        return high + 1

Java 代码实现

class Solution {
    public int searchInsert(int[] nums, int target) {
        int low = 0;
        int high = nums.length - 1;
        while (low <= high) { 
            int mid = low + (high - low) / 2; // 防止溢出
            if (nums[mid] > target) {
                high = mid - 1; 
            } else if (nums[mid] < target) {
                low = mid + 1; 
            } else {
                return mid;
            }
        }
        return high + 1;
    }
}

图解搜索插入位置到这就结束了。

还是要强调一下,二分查找本身是没难度的,但是二分查找是个细节控,稍不注意就容易出错

大家在应用二分查找的时候还是要谨慎谨慎再谨慎。

记得帮我点赞呀,么么哒。

我是帅蛋,我们下次见。