大厂面试高频题——二分查找

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

思考

二分模板题

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

BM21 旋转数组的最小数字(有重复数字)

思考

剑指offer上原题。数组划分为2个区间,二分搜索逐渐逼近最小值。遇到无法判断区间时,需要顺序逐个查找。

class Solution:
    def minNumberInRotateArray(self , nums: List[int]) -> int:
        # write code here
        left = 0
        right = len(nums)-1
        mid = 0
        while nums[left]>=nums[right]:
            if right-left == 1:
                return nums[right]
            mid = int((left+right)/2)
            if nums[left] == nums[right] and nums[left] == nums[mid]:
                res = nums[left]
                for i in range(left+1,right+1):
                    if nums[i] < res:
                        res = nums[i]
                return res
            if nums[mid] >= nums[left]:
                left = mid
            elif nums[mid] <= nums[right]:
                right = mid
        return nums[left]
  1. 搜索旋转排序数组 (无重复数字搜索指定target)
    整数数组 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) 的算法解决此问题。

思考

根据mid值判断是左有序 还是 右有序。在有序的区间内进行二分查找。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums)-1
        #[left,right]
        mid = -1
        while left<=right:
            mid = int((left+right)/2)
            if nums[mid] == target:
                return mid
            if nums[mid] >= nums[left]:
                #左有序
                if target >= nums[left] and target < nums[mid]:
                    # 二分
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                #右有序
                if target > nums[mid] and t arget <= nums[right]:
                    left = mid + 1
                else:
                    right = mid -1
        return -1

153.寻找旋转排序数组中的最小值(无重复)

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 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) 的算法解决此问题。

思考

比有重复的要简单一点。两种方法。
方法1:剑指offer版本,比较容易理解:划分为2个区间,逐渐逼近结果。

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

方法2:leetcode官方题解。
在二分查找的每一步中,左边界为 low,右边界为 high,区间的中点为 pivot,最小值就在该区间内。我们将中轴元素 nums[pivot] 与右边界元素 nums[high] 进行比较,可能会有以下的三种情况:


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

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

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

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

思考

划分为2个区间 <target 和 >=target 来寻找左边界。
右边界通过寻找targe+1的左边界-1来代替。优雅

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def getRightBorder(nums, target):
            left = 0 
            right = len(nums)-1
            #right_border = -2
            while left<=right:
                mid = int((left+right)/2)
                if nums[mid] > target:
                    right = mid - 1
                else:
                    left = mid + 1
            return right
        
        def getLeftBorder(nums, target):
            left = 0 
            right = len(nums)-1
            #left_border = -2
            while left<=right:
                mid = int((left+right)/2)
                if nums[mid] < target:
                    left = mid + 1
                else:
                    right = mid - 1
                    #left_border = right
            return left
        
        left_border = getLeftBorder(nums, target)
        #right_border = getRightBorder(nums,target)
        right_border = getLeftBorder(nums, target+1) - 1
        if left_border == len(nums) or nums[left_border] != target:
            return [-1,-1]
        else:
            return [left_border,right_border]
posted @ 2024-07-08 17:23  forrestr  阅读(27)  评论(0)    收藏  举报