LeetCode热题100二分查找题解析

165 阅读6分钟

难度标识:⭐:简单,⭐⭐:中等,⭐⭐⭐:困难

tips:这里的难度不是根据LeetCode难度定义的,而是根据我解题之后体验到题目的复杂度定义的。

1.搜索插入位置

思路

解这题的思路就是使用二分查找不断的缩小查找范围。具体做法就是拿左右两个指针去缩小查找范围。

  1. 初始化两个指针:一个在数组的开始位置(left),另一个在数组的结束位置(right)。

  2. 循环直到left超过right结束

  3. 在每次迭代中,计算中间索引 mid = (left + right) / 2

  4. 比较中间元素与目标值:

    • 如果中间元素等于目标值,那么你已经找到了目标并且可以返回 mid
    • 如果目标值大于中间元素,这意味着目标值应该在数组的右半部分,因此将 left 设置为 mid + 1
    • 否则,目标值应该在数组的左半部分,所以将 right 设置为 mid - 1
  5. left超过right时,跳出循环。此时,left 指针会指向目标值应该插入的位置。

返回left作为答案。

这种方法的时间复杂度是O(log n),因为你在每次迭代中都将搜索空间减少了一半。

代码

var searchInsert = function (nums, target) {
    let left = 0, right = nums.length - 1
    while (left <= right) {
        const mid = Math.floor((left + right) / 2)
        if (nums[mid] === target) {
            return mid
        } else if (nums[mid] < target) {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return left
};

2. 搜索二维矩阵

这题跟 LeetCode热题100矩阵题解析里的第4题搜索二维矩阵 II解法一模一样,II都能解决,那更别说I了,这题其实很简单。

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

思路

这题我们可以使用独立的两次二分查找,分别找到要查找元素的左起点和右起点。

  1. 第一次查找目标值的开始位置。
  2. 第二次查找目标值的结束位置。

以下是核心思路:

  1. 查找目标值的开始位置

    • 使用标准的二分查找,但当找到目标值时,不要立即返回。
    • 而是继续在左侧搜索(即,使right指针移动到mid - 1),直到确定左侧没有相同的目标值为止。
    • 最后,left指针将指向目标值的开始位置。
  2. 查找目标值的结束位置

    • 再次使用二分查找,但这次当找到目标值时,你要继续在右侧搜索(即,使left指针移动到mid + 1)。
    • 直到确定右侧没有相同的目标值为止。
    • 最后,right指针将指向目标值的结束位置。
  3. 如果在上述两个步骤中,你没有找到目标值,那么应该返回 [-1, -1]

这种方法的时间复杂度为O(log n)。因为我们只是进行了两次标准的二分查找。

代码

var searchRange = function (nums, target) {
    function findLeft() {
        let left = 0, right = nums.length - 1
        while (left <= right) {
            const mid = Math.floor((left + right) / 2)
            if (nums[mid] < target) {
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
        return left
    }
    function findRight() {
        let left = 0, right = nums.length - 1
        while (left <= right) {
            const mid = Math.floor((left + right) / 2)
            if (nums[mid] <= target) {
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
        return right
    }
    let leftIndex = findLeft(), rightIndex = findRight()
    if (leftIndex <= rightIndex) {
        return [leftIndex, rightIndex]
    }
    return [-1, -1]
};

4.搜索旋转排序数组

思路

要在时间复杂度为 O(logn) 的条件下解决这个问题,我们可以使用二分查找。但由于数组被旋转了,所以我们不能直接应用传统的二分查找。我们需要稍微修改策略。

核心思路如下:

  1. 确定中点和旋转的部分:首先,找到中点。这可以通过 (left + right) / 2 来确定。接着,我们要确定哪部分是有序的。根据旋转的特点,至少有一半是有序的。

  2. 使用有序的部分:当我们确定了哪部分是有序的,我们可以检查目标值 target 是否在这个有序的范围内。如果是,我们可以在这个范围内进行二分查找;如果不是,那么我们可以在另一半中查找。

  3. 递归或迭代:根据上述逻辑,我们可以递归地进行查找,或者使用一个循环来迭代地进行查找。

代码

var search = function (nums, target) {
    let left = 0, right = nums.length - 1
    while (left <= right) {
        const mid = Math.floor((left + right) / 2)
        if (nums[mid] === target) {
            return mid
        }
        if (nums[left] <= nums[mid]) {
            if (nums[left] <= target && target < nums[mid]) {
                right = mid - 1
            } else {
                left = mid + 1
            }
        } else {
            if (nums[mid] < target && target <= nums[right]) {
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
    }
    return -1
};

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

思路

解这个问题的答案与旋转数组的特性有关。首先,我们知道原始数组是升序排列的。当我们对数组进行旋转时,其实就是将数组前面的一段移到后面去。

  1. 如果一个旋转数组的中间元素比最右侧元素大,这意味着中间元素是来自原始数组的左边部分(也就是较大的部分),而最右侧的元素来自原始数组的右边部分(也就是较小的部分)。所以,这种情况下的旋转点(也就是最小元素的位置)一定在中间元素的右侧。

    例如,考虑数组 [4,5,6,7,0,1,2]。如果我们选择中间的元素7,它比最右侧的元素2要大,说明最小元素0位于7的右侧。

  2. 如果中间元素比最右侧元素小或等于,这意味着中间元素来自原始数组的右边部分(也就是较小的部分)。这时,旋转点可能就在中间元素的位置,或者在它的左侧。

根据上述原理,我们可以确定在哪一半继续进行搜索。

代码

var findMin = function (nums) {
    let left = 0, right = nums.length - 1;
    let minVal = 0
    while (left < right) {
        const mid = Math.floor((left + right) / 2)
        if (nums[mid] > nums[right]) {
            left = mid + 1
        } else {
            right = mid
        }
    }
    return nums[left]
};

6. 寻找两个正序数组的中位数 ⭐⭐⭐

思路

解这题也是使用二分查找的方法。但是这题说实话还是有点难的,我做了那么多题,我觉得这题比前面的题都难一些,题解视频我看了几遍才明白,简单来说就相当于使用一个分割线去给数组进行大数小数分组从而找到中位数的位置,这题得看视频理解学习,不懂的话就多看几遍

代码

var findMedianSortedArrays = function (nums1, nums2) {
    if (nums1.length > nums2.length) {
        [nums1, nums2] = [nums2, nums1]
    }
    let m = nums1.length, n = nums2.length;
    let halfLen = Math.floor((m + n + 1) / 2)
    let left = 0, right = m
    while (left <= right) {
        let mid = Math.floor((left + right) / 2)
        let j = halfLen - mid

        let num1Left = mid === 0 ? -Infinity : nums1[mid - 1]
        let num1Right = mid === m ? Infinity : nums1[mid]
        let num2Left = j === 0 ? -Infinity : nums2[j - 1]
        let num2Right = j === n ? Infinity : nums2[j]
        if (num1Left <= num2Right && num2Left <= num1Right) {
            if ((m + n) % 2 === 1) {
                return Math.max(num1Left, num2Left)
            } else {
                return (Math.max(num1Left, num2Left) + Math.min(num1Right, num2Right)) / 2
            }
        }
        if (num1Left > num2Right) {
            right = mid - 1
        } else {
            left = mid + 1
        }
    }
};

总体来说,二分查找的题目除了上面的最后一题需要多看几遍视频理解一些,其他的还是比较简单的。