二分查找 35. 搜索插入位置 34. 在排序数组中查找元素的第一个和最后一个位置 33. 搜索旋转排序数组

35. 搜索插入位置

  • 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
    你可以假设数组中无重复元素。

示例:

示例 1:
输入: [1,3,5,6], 5
输出: 2

示例 2:
输入: [1,3,5,6], 2
输出: 1

示例 3:
输入: [1,3,5,6], 7
输出: 4

示例 4:
输入: [1,3,5,6], 0
输出: 0

思路:迭代

常规二分查找思路,判断左右大小关系时需加等号,为了返回不存在值的正确插入位置

代码实现:

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

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

  • 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
    你的算法时间复杂度必须是 O(log n) 级别。
    如果数组中不存在目标值,返回 [-1, -1]。

示例:

示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

思路1:递归

首先寻找上边界和下边界
如例1:
8成为下边界的条件:

  1. 该数必须是8
  2. 该数的左边一个数不能是8
    左边有数,那么该数必须小于8
    左边没有数,即8是数组的第一个数

8成为上边界的条件:

  1. 该数必须是8
  2. 该数的右边一个数不能是8
    右边有数,那么该数必须大于8
    右边没有数,即8是数组的最后一个数

代码实现:

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if not nums:
            return [-1, -1]
		
		# 查找下限
        def search_lower_bound(nums, target, low, high):
            if low > high:
                return -1
            mid = (low + high) // 2
            if nums[mid] == target and (mid == 0 or nums[mid - 1] < target):
                return mid
            if target <= nums[mid]:
                return search_lower_bound(nums, target, low, mid - 1)
            else:
                return search_lower_bound(nums, target, mid + 1, high)

		# 查找上限
        def search_upper_bound(nums, target, low, high):
            if low > high:
                return -1
            mid = (low + high) // 2
            if nums[mid] == target and (mid == len(nums)-1 or nums[mid + 1] > target):
                return mid
            if target >= nums[mid]:
                return search_upper_bound(nums, target, mid + 1, high)
            else:
                return search_upper_bound(nums, target, low, mid - 1)

        return [search_lower_bound(nums, target, 0, len(nums)-1), search_upper_bound(nums, target, 0, len(nums)-1)]

思路2:迭代

代码实现:

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if not nums:
            return [-1, -1]
        n = len(nums)

        def helper(first):
            left = 0
            right = n - 1
            while left <= right:
            	# if elif 为二分查找逻辑
                mid = (left + right) // 2
                if target < nums[mid]:
                    right = mid - 1
                elif target > nums[mid]:
                    left = mid + 1
                elif target == nums[mid]:
                    # 查找上界
                    if first:
                        if nums[mid] == target and (mid == 0 or nums[mid] > nums[mid - 1]):
                            return mid
                        # 不满足时,说明当前值在界内且不是上界,需将右边界左移,再次寻找
                        else:
                            right -= 1
                    # 查找下界
                    else:
                        if nums[mid] == target and (mid == n - 1 or nums[mid] < nums[mid + 1]):
                            return mid
                        # 不满足时,说明当前值在界内且不是上界,需将左边界右移,再次寻找
                        else:
                            left += 1
            return -1

        return [helper(True), helper(False)]

33. 搜索旋转排序数组

  • 假设按照升序排序的数组在预先未知的某个点上进行了旋转。
    ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
    搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -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

思路:

首先判断列表是否有序:

  1. 若有序,直接进行二分查找
  2. 若无序,先找到数组中最小值的索引
    1. 如果目标值大于等于列表第一个数,说明目标值的索引在 0 到 最小值索引-1 之间
    2. 否则,说明目标值的索引在 最小值索引 到 列表末尾的索引 之间

在找最小值索引的函数中,

  1. 若mid左边或右边不满足升序:
    如果mid不是最左边,且比前一个值小,说明mid为最小值索引
    例如:[4, 0, 1, 2],当mid=1对应的值为0时,为最小值索引
    如果mid不是最右边,且比后一个值大,说明mid + 1为最小值索引
    例如:[4, 5, 6, 1],当mid=2对应的值为6时,mid + 1 = 3为最小值索引
  2. 否则,说明mid在正常序列之中,此时需判断:
    mid对应的值若大于最左端,例如:[3, 4, 5, 6, 7, 0, 1],mid=3,对应的值为6,说明左边有序,最小值索引在右半边
    mid对应的值若小于最右端,例如:[5, 6, 0, 1, 2, 3, 4],mid=3,对应的值为1,说明右边有序,最小值索引在左半边

代码实现:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if not nums:
            return -1

        def search_min_index(left, right):
            while left <= right:
                mid = (left + right) // 2
                if mid < right and nums[mid] > nums[mid + 1]:
                    return mid + 1
                elif mid > left and nums[mid] < nums[mid - 1]:
                    return mid
                if nums[left] < nums[mid]:
                    # 左边有序,右边无序,在右边寻找最小值
                    left = mid + 1
                elif nums[mid] < nums[right]:
                    # 右边有序,左边无序,在左边寻找最小值
                    right = mid - 1
                else:
                    return mid

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

		n = len(nums)
        left = 0
        right = n - 1
        if nums[0] <= nums[-1]:
            # 说明nums有序,直接使用二分查找
            return binary_search(left, right)
        else:
            # 先找到最小值的索引
            min_index = search_min_index(left, right)
            if nums[0] <= target:
                # 如果目标值大于等于第一个数,说明目标值的索引在 0 到 最小值索引-1 之间
                return binary_search(left, min_index - 1)
            else:
                # 否则,说明目标值的索引在 最小值索引 到 列表末尾的索引 之间
                return binary_search(min_index, right)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值