Skip to content

勇闯秋招手撕系列 VI:二分搜索

约 1366 个字 127 行代码 2 张图片 预计阅读时间 6 分钟 总阅读量

搜索旋转排序数组

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

搜索二维矩阵

排序数组内元素第一个和最后一个位置(🌟)

寻找峰值

寻找两个正序数组的中位数

35. 搜索插入位置

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

请必须使用时间复杂度为 O(log n) 的算法。

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        n = len(nums)
        l = 0; r = n - 1
        while l <= r:
            mid = (r - l) // 2 + l 
            if nums[mid] == target:
                return mid 
            elif nums[mid] > target:
                r = mid - 1
            else:
                l = mid + 1
        return l

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

难度:Medium 中等 ;已知一个长度为 n 的数组,预先按照升序排列,经由 1n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到: 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2] 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7] 注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,是一个升序排列的数组按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。时间复杂度为O(log n).

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

输出:1

class Solution:
    def findMin(self, nums: List[int]) -> int:
        min_ = nums[0]
        l = 0
        r = len(nums) - 1
        while l <= r:
            mid = l + (r - l) // 2
            if nums[mid] >= min_:
                l = mid + 1
            else:
                min_ = nums[mid]
                r = mid - 1
        return min_

二分搜索,但是每次不是比较mid和target,而是比较mid和最小值,所以需要记录每次搜索时的最小值。


74. 搜索二维矩阵🌟

给你一个满足下述两条属性的 m x n 整数矩阵:

每行中的整数从左到右按非严格递增顺序排列。 每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        m = len(matrix)
        n = len(matrix[0])
        r = 0; c = n - 1
        while r < m and c >= 0:
            if matrix[r][c] == target:
                return True 
            elif matrix[r][c] < target:
                r += 1
            else:
                c -= 1
        return False

思路:两次二分搜索!一次针对行、一次针对列


33. 搜索旋转排序数组

难度:Medium 中等 ;整数数组 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 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

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

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        l = 0; r = len(nums) - 1
        while l <= r:
            mid = l + (r - l) // 2
            if nums[mid] == target:
                return mid 
            if nums[mid] >= nums[l]:
                if nums[l] <= target < nums[mid]:
                    r = mid - 1
                else:
                    l = mid + 1
            else:
                if nums[mid] < target <= nums[r]:
                    l = mid + 1
                else:
                    r = mid - 1
        return -1

牢记:二分搜索一定只能在有序的部分进行查找。而问题这么旋转了数组后,mid在的部分一定属于一个有序的部分。我们只需要判断target是否在这个有序的部分里,如果在,就缩小区间到这个有序的部分里,如果不在,就缩小区间到另一个部分。

比如,有两种划分方法:第一种是,看了最左侧的和中间的,发现mid属于前半部分,那么,只需要判断target是否在前半部分,也就是在left, mid之间:如果在,就缩小区间到前半部分,如果不在,就缩小区间到后半部分。第二种是mid属于后半部分,那么,只需要判断target是否在后半部分,也就是 mid, right 之间。如果在,就缩小区间到后半部分,如果不在,就缩小区间到前半部分。


🌟34. 排序数组内查找元素第一/最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target ,返回 [-1, -1]。你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

输入:nums = [5,7,7,8,8,10], target = 8

输出:[3,4]

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        result = [-1, -1]
        n = len(nums)
        l = 0; r = n - 1
        while l <= r:
            mid = l + ( r - l) // 2
            if nums[mid] > target:
                r = mid - 1
            elif nums[mid] < target:
                l = mid + 1
            else:
                result[0] = mid
                r = mid - 1
        l = 0; r = n - 1
        while l <= r:
            mid = l + ( r - l) // 2
            if nums[mid] > target:
                r = mid - 1
            elif nums[mid] < target:
                l = mid + 1
            else:
                result[1] = mid
                l = mid + 1
        return result

一开始题解没看懂,实际没那么复杂...要想找到最前面的那个,无非就是正常二分搜索过程中找到了一个目标值了,但是可能前面还有。此时我们保存下当前的位置,但是调整右边的pivot,依然继续搜索,直到两个指针逼近;要想找到最后面的那个,无非是正常二分搜索过程中找到目标值了,但是可能后面还有,此时我们保存下当前的位置,但是调整左边的poivot,依然继续搜索,直到两个指针逼近


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

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

输入:nums1 = [1,3], nums2 = [2] 输出:2.00000

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        if len(nums1) > len(nums2):
            nums1, nums2 = nums2, nums1 
        m = len(nums1)
        n = len(nums2)
        left = 0
        right = m 
        while left <= right:
            l = (right + left) // 2
            r = (m + n + 1) // 2 - l 
            leftMin1 = float('-inf') if l == 0 else nums1[l - 1]
            leftMin2 = float('-inf') if r == 0 else nums2[r - 1]
            rightMax1 = float('inf') if l == m else nums1[l]
            rightMax2 = float('inf') if r == n else nums2[r]
            if leftMin1 <= rightMax2:
                med1 = max(leftMin1, leftMin2)
                med2 = min(rightMax1, rightMax2)
                left = l + 1
            else:
                right = l - 1
        if (m + n) % 2 == 0:
            return (med1 + med2) / 2.0
        else:
            return med1

162. 寻找峰值

峰值元素是指其值严格大于左右相邻值的元素。一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

假设 nums[-1] = nums[n] = -∞ 。你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

输入:nums = [1,2,3,1] 输出:2 解释:3 是峰值元素,你的函数应该返回其索引 2。

class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        m = len(nums)
        def get(idx):
            if idx < 0 or idx >= m:
                return float('-inf')
            return nums[idx]
        l = 0; r = m - 1
        while l <= r:
            mid = (r - l ) // 2 + l 
            if nums[mid] > get(mid - 1) and nums[mid] > get(mid + 1):
                return mid 
            elif nums[mid] <= get(mid + 1):
                l = mid + 1
            else:
                r = mid - 1
        return mid