数据结构与算法——日取其半之二分查找

一、搜索选择排序数组

此题为leetcode第33题
思路:和常规的二分法套路差不多,定义左右指针left和right,求得中间值mid,此时有一半数组肯定是有序的,如下图所示。如果nums[left] < nums[mid],那就是[left, mid]有序,如果nums[left] <= target < nums[mid],那么就在左半边找,否则在右半边找。如果有nums[mid] < nums[right],那就是[mid, right]有序,如果nums[mid] < target <= nums[right],那么就在右半边找,否则在左半边找。
在这里插入图片描述

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left, right = 0, n - 1
        while left <= right:
            # [left:mid]和[mid+1:right]至少有一个是有序的
            mid = left + (right - left) // 2
            if target == nums[mid]:
                return mid
            # 如果左半边是有序的
            if nums[left] <= nums[mid]:
                if nums[left] <= target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            # 如果右半边是有序的
            else:
                if nums[mid] < target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
        return -1

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

此题为leetcode第34题
思路:我们需要搜索两次,一次搜索目标值所在区间的左边界,另一次搜索目标值所在区间的右边界。在搜索左边界的时,找到target值后不能马上停止,要继续搜索,直到left==right。找右边界时正常搜索就可以。

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        left_index = self.search(nums, target)
        
        if left_index == len(nums) or nums[left_index] != target:
            return [-1, -1]
        
        right_index = self.search(nums, target, False)
        return [left_index, right_index - 1]
        
    
    def search(self, nums, target, search_left=True):
        left, right = 0, len(nums)
        
        while left < right:
            mid = left + (right - left) // 2
            if target < nums[mid] or (search_left and target == nums[mid]):
                right = mid
            else:
                left = mid + 1
        return right

三、搜索插入位置

此题为leetcode第35题
思路:和基本的二分查找几乎一模一样,不同的是当target不存在时我们要找到它插入的位置,最后返回left即可

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

四、x的平方根

此题为leetcode第69题
思路:对于x=0或1的情况直接返回x即可。初始left=2,right=x//2,直接套用二分查找,如果不存在mid*mid==target,那么最后返回right。

class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0 or x == 1:
            return x
        
        left, right = 2, x // 2
        while left <= right:
            mid = left + (right - left) // 2
            if mid * mid == x:
                return mid
            elif mid * mid < x:
                left = mid + 1
            else:
                right = mid - 1
        return right

五、搜索二维矩阵

此题为leetcode第74题
思路:此题的二维矩阵其实相当于一维有序数组reshape成了二维数组,我们可以还按一维数组的方式二分查找,只不过left、right和mid要转为二维矩阵的行列坐标。设矩阵形状为m行n列,初始left = 0,right = m * n - 1,mid依然是left + (right - left) // 2,那么mid对应的坐标为(mid // n, mid % n)。

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        if len(matrix) == 0 or len(matrix[0]) == 0:
            return False
        
        m, n = len(matrix), len(matrix[0])
        length = m * n
        left, right = 0, length - 1
        while left <= right:
            mid = left + (right - left) // 2
            row, col = mid // n, mid % n
            if matrix[row][col] == target:
                return True
            elif matrix[row][col] < target:
                left = mid + 1
            else:
                right = mid - 1
        return False

六、寻找峰值

此题为leetcode第162题
思路:当nums[mid] < nums[mid + 1]时,说明mid处于上升阶段,峰值肯定在mid右边,左边可抛去,left = mid + 1;当nums[mid] > nums[mid + 1]时,说明mid处于下降阶段,峰值肯定在mid左边,右边可以抛去,right = mid。那么峰值会在[left, right)区间,注意while循环条件是left < right。

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

七、寻找重复数

此题为leetcode第287题
思路:我们可以遍历数组,对于nums[i]我们搜索在数组中是否存在第二个这样的数,但这样的时间复杂度是O(n^2)。由于数字都在1到n之间,我们可以使用二分法查找哪个数字重复了。设left = 1,right = n,在每次循环时,我们统计nums里小于等于mid的个数count。如果mid不是重复的元素,那么count == mid,否则重复的元素肯定在[left, mid]里。

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        n = len(nums)
        left, right = 1, n - 1
        while left < right:
            mid = left + (right - left) // 2
            
            # 统计num <= mid的个数
            count = 0
            for num in nums:
                if num <= mid:
                    count += 1
            # 如果count大于mid,说明重复元素一定在[left, mid]里
            if count > mid:
                right = mid
            else:   # 否则元素在(mid, right]里
                left = mid + 1
        return left
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值