Leetcode 二分查找 2

题号直达: 34 查找区间 | 33 搜索旋转排序数组 | 153 旋转数组最小值 | 4 两数组中位数

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

题目

中等。 给定按非递减顺序排序的整数数组 nums 和目标值 target,找出 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]

提示

  • 0 ≤ len(nums) ≤ 10^5
  • -10^9 ≤ nums[i], target ≤ 10^9,nums 非递减

代码(Python)解法一:先找任意命中,再向两侧二分边界

  1. 第一次二分:在整段上找任意一个 nums[mid] == target,记下 ini_ind;若无则返回 [-1,-1]。
  2. 左边界:在 [0, ini_ind] 上二分,找最靠左的 target:若 nums[mid] == target 且(mid == 0 或 nums[mid-1] < nums[mid]),则 mid 为左端点;若 nums[mid] == target 且左侧仍为 target,则向左半继续。
  3. 右边界:在 [ini_ind, len(nums)-1] 上二分,找最靠右的 target:若 nums[mid] == target 且(mid == len-1 或 nums[mid+1] > nums[mid]),则 mid 为右端点;否则向右半继续。
class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        left = 0
        right = len(nums) - 1
        ini_ind = -1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                ini_ind = mid
                break
            elif nums[mid] > target:
                right = mid - 1
            else:
                left = mid + 1
        if ini_ind == -1:
            return [-1, -1]

        right = ini_ind
        left = 0
        left_ind = -1
        while left <= right:
            mid = (left + right) // 2
            if mid == 0 and nums[mid] == target:
                left_ind = 0
                break
            elif nums[mid] < target:
                left = mid + 1
            elif nums[mid - 1] < nums[mid] and nums[mid] == target:
                left_ind = mid
                break
            else:
                right = mid - 1

        right = len(nums) - 1
        left = ini_ind
        right_ind = -1
        while left <= right:
            mid = (left + right) // 2
            if mid == len(nums) - 1 and nums[mid] == target:
                right_ind = len(nums) - 1
                break
            elif nums[mid] > target:
                right = mid - 1
            elif nums[mid + 1] > nums[mid] and nums[mid] == target:
                right_ind = mid
                break
            else:
                left = mid + 1

        return [left_ind, right_ind]

思路解析(解法一)

  • 三次二分:第一次在全局定位 target 是否存在;后两次在缩小的区间内分别找「第一个」与「最后一个」target,均保持 O(log n)(常数因子为 3,仍为 O(log n))。
  • 左边界:nums[mid] == target 且左邻更小(或已在下标 0),即为区间左端。
  • 右边界:nums[mid] == target 且右邻更大(或已在下标 len-1),即为区间右端。
  • 空数组:第一次二分 len(nums)==0 时 right=-1,循环不进入,ini_ind 保持 -1,返回 [-1,-1]。

代码(Python)解法二:lower_bound / upper_bound 两次二分

抽象出 binarySearch(nums, target, lower):在有序数组上找第一个满足条件的下标并写入 ans。

  • lower=True:找第一个 nums[i] >= target(即 lower_bound),对应区间左端点。
  • lower=False:找第一个 nums[i] > target(即 upper_bound),右端点为 该下标 - 1。

最后校验 leftIdx ≤ rightIdx 且两端均为 target,否则无命中。

class Solution(object):
    def binarySearch(self, nums, target, lower):
        left, right = 0, len(nums) - 1
        ans = len(nums)

        while left <= right:
            mid = (left + right) // 2
            if nums[mid] > target or (lower and nums[mid] >= target):
                right = mid - 1
                ans = mid
            else:
                left = mid + 1

        return ans

    def searchRange(self, nums, target):
        leftIdx = self.binarySearch(nums, target, True)
        rightIdx = self.binarySearch(nums, target, False) - 1

        if (
            leftIdx <= rightIdx
            and rightIdx < len(nums)
            and nums[leftIdx] == target
            and nums[rightIdx] == target
        ):
            return [leftIdx, rightIdx]

        return [-1, -1]

思路解析(解法二)

  • 统一模板:nums[mid] > target 或(lower 且 nums[mid] >= target)时向左收缩并记录 ans,否则向右;结束时 ans 为第一个「大于 / 大于等于」分界位置。
  • 右端点:upper_bound 是第一个 > target 的位置,-1 即最后一个 == target。
  • 合法性:若不存在 target,leftIdx 可能落在第一个 > target 处或越界侧,通过区间与端点值判断返回 [-1,-1]。
  • 复杂度:两次二分,O(log n) 时间,O(1) 额外空间。

LeetCode 33. 搜索旋转排序数组 ↑

题目

中等。 整数数组 nums 原本升序且互不相同,在某个未知下标处旋转后得到当前数组(例如 [0,1,2,4,5,6,7] 在下标 3 处旋转得到 [4,5,6,7,0,1,2])。给定 nums 与整数 target,若 target 在数组中则返回其下标,否则返回 -1。

要求: 时间复杂度 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

提示

  • 1 ≤ len(nums) ≤ 5000
  • -10^4 ≤ nums[i] ≤ 10^4,元素互不相同
  • nums 由升序数组在某一未知下标处旋转得到
  • -10^4 ≤ target ≤ 10^4

代码(Python):先找旋转点(最小值下标),再二分

步骤 1: 用二分找最小值下标 pivot(nums[mid] > nums[right] 则最小值在右半段,否则在左半段含 mid)。步骤 2: 比较 target 与 nums[-1]:若 target ≤ nums[-1],则 target 只可能落在右半段有序区间 [pivot, n-1],否则落在左半段 [0, pivot-1]。步骤 3: 在选定区间上做普通二分。

class Solution(object):
    def search(self, nums, target):
        if not nums:
            return -1

        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right) // 2
            if nums[mid] > nums[right]:
                left = mid + 1
            else:
                right = mid
        pivot = left

        if target <= nums[-1]:
            left, right = pivot, len(nums) - 1
        else:
            left, right = 0, pivot - 1

        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        return -1

思路解析

  • 旋转点:最小值所在位置把数组分成两段,各自内部仍升序;与 nums[right] 比较可判断最小值在左还是右。
  • 定区间:右段元素均 ≤ nums[-1],左段元素均 > nums[-1](无重复时成立),据此二选一做第三次二分。
  • 空数组:直接返回 -1(与题目主数据 len ≥ 1 可并存)。
  • 复杂度:两次二分过程均为 O(log n),总计 O(log n),O(1) 额外空间。

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

题目

中等。 长度为 n 的数组原本升序且元素互不相同,经过 1~n 次「旋转」 得到 nums。一次旋转指把最后一个元素移到最前面。

给定旋转后的 nums,返回其中的最小值。

要求: 时间复杂度 O(log n)。

测试样例

  • 示例 1: nums = [3,4,5,1,2]
    输出:1

  • 示例 2: nums = [4,5,6,7,0,1,2]
    输出:0

  • 示例 3: nums = [11,13,15,17]
    输出:11(旋转后仍整体升序,最小值即首元素)

提示

  • n == len(nums),1 ≤ n ≤ 5000
  • -5000 ≤ nums[i] ≤ 5000,互不相同
  • nums 由升序数组经 1~n 次旋转得到

代码(Python):二分找最小值下标

与 33 题 中「找旋转点」相同:nums[mid] > nums[right] 时最小值在 mid 右侧,否则在 [left, mid]。循环结束时 left == right 即为最小值下标,返回 nums[pivot]。

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if not nums:
            return -1

        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right) // 2
            if nums[mid] > nums[right]:
                left = mid + 1
            else:
                right = mid
        pivot = left

        return nums[pivot]

思路解析

  • 二分性质:右半段(含最小值)的数整体不超过 nums[right] 一侧的「断点」;用与末尾比较可不断砍掉不含最小值的一半。
  • 与 33 题关系:本题的 pivot 就是 33 题里第一步求出的最小值下标,此处只需返回值 nums[pivot]。
  • 空数组:题目数据一般 n ≥ 1;若保留 if not nums: return -1,需知这与部分 OJ 对返回类型的约定可能不一致,可按需要删去。
  • 复杂度:O(log n) 时间,O(1) 额外空间。

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

题目

困难。 给定两个大小分别为 m、n 的正序(升序)数组 nums1 与 nums2,求两个数组合并后中位数(若总个数为偶数,取中间两数的平均值)。

要求: 时间复杂度 O(log(m+n))。

测试样例

  • 示例 1: nums1 = [1,3],nums2 = [2]
    输出:2.00000(合并为 [1,2,3],中位数为 2)

  • 示例 2: nums1 = [1,2],nums2 = [3,4]
    输出:2.50000(合并为 [1,2,3,4],中位数为 (2+3)/2)

提示

  • len(nums1) == m,len(nums2) == n
  • 0 ≤ m, n ≤ 1000,且 1 ≤ m + n ≤ 2000
  • -10^6 ≤ nums1[i], nums2[i] ≤ 10^6

代码(Python)解法一:合并后排序

将两数组合并为 nums1 + nums2,sort() 后取中位:长度为偶数时取中间两项的平均值(用 float(...)),为奇数时取正中间元素。

class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        num = nums1 + nums2
        num.sort()
        if len(num) % 2 == 0:
            return float(num[len(num) // 2] + num[len(num) // 2 - 1]) / 2
        else:
            return float(num[len(num) // 2])

思路解析(解法一)

  • 正确性:合并排序后,中位数定义与题意一致。
  • 复杂度:排序主导,为 O((m+n) log(m+n)) 时间,O(m+n) 额外空间(合并后的列表);不满足题面 O(log(m+n)) 要求,适合理解题意与对拍。
  • 改进:需在两个有序数组上做划分 / 二分,使找第 k 小或左右部分元素个数满足中位数定义,可达到 O(log(m+n))(可作为解法二单独整理)。
posted @ 2026-10-12 00:22  Phier  阅读(1)  评论(0)    收藏  举报