leetcode 33、34、35 各种条件下的二分查找小结 Python

2022年5月更新
之前写的代码不是很好,参考我写的下面这几题的解答,有机会重写一下。

leetcode 35简单. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法
输入: nums = [1,3,5,6], target = 5
输出: 2
输入: nums = [1,3,5,6], target = 2
输出: 1
输入: nums = [1,3,5,6], target = 7
输出: 4

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

leetcode 33中等. 搜索旋转排序矩阵

整数数组 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 。
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
输入:nums = [1], target = 0
输出:-1

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

        n=len(nums)
        if n==0:
            return -1
        max_index=nums.index(max(nums))
        ori_nums_1=nums[:max_index+1] #bigger
        ori_nums_2=nums[max_index+1:] # smaller

        index_1=bs(ori_nums_1,target)
        index_2=bs(ori_nums_2,target)
        # print(index_1 , index_2)
        if index_1==-1 and index_2==-1:
            return -1
        if index_1!=-1:
            return index_1
        if index_2!=-1:
            return index_2+len(ori_nums_1)

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

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        n=len(nums)
        if n==0:
            return [-1,-1]
        l, r = 0, n-1
        while l<r: # 如果l=r=mid,那么因为相同元素存在,循环可能不会结束
            mid=l+(r-l)//2
            if nums[mid]<target:
                l=mid+1
            else: #也就是说当target等于nums[mid]的时候
            #最右边会变成target大小,然后left不断右移,直到第一次left>=right
            #而此时的left,就是target大小的元素第一次出现的位置
                r=mid
        print(l)
        first_index=r if nums[r]==target else -1
        return [first_index, last_index]
		
		l , r = 0, n-1
        while l<r:
            mid=l+(r-l+1)//2 # 保证l偏向右边
            if nums[mid]>target:
                r=mid-1
            else:
                l=mid
        print(l)
        last_index=l if nums[l]==target else -1
        

-------------------旧版文章--------------------------------
二分查找使用有两个条件

  1. 表必须有序(增或减,可包含重复值)
  2. 表必须为顺序存储结构【数据结构可以分成栈,队列,线性表;线性表下有顺序存储结构和链式存储结构】

用起来的时候情况很多,但是都可以根据一个基本架构来稍微改变条件
题目0,返回target第一次出现的位置,注意target必然在nums中。

def bs0(nums,target):
	l,r=0,len(nums)-1
	while(l<r):
		mid=l+((r-l)>>1) 
		# >>1是除以二的操作,结果向下取整,7>>1=3
		#写成(l+r)/2可能会溢出
		if(target<=nums[mid]):
		#将right左移 进而mid左移 匹配第一次出现的
			r=mid
		else:
			l=mid+1
	return l

先来理解一下这段代码,要得到第一次出现的位置,最终返回的必然是left的值,我们不关心target最后出现的位置。所以当target<=nums[mid]时候,我们就可以将right左移到mid处。
这局代码使得我们不关心
首先看这一段代码,它的输出是:
第一个等于target的元素值的index(存在元素值等于target),比如
[1,1,2,3,4,4,5],target=2; 输出是2
第一个大于target的元素值的index(不存在元素值等于target),比如
[1,1,2,2,4,4,5],target=3; 输出是4
[1,1,2,2,4,4,5],target=6; 输出是7
但是这里index=7是不存在的,一般会要求返回-1,所以代码变成了

def bs1(nums,target):
	left,right=0,len(nums)-1
	while(left<right):
		mid=left+((right-left)>>1) #话说之前这里掉了个括号。。。
		if(target<=nums[mid]):
			right=mid
		else:
			left=mid+1
	return left if left<len(nums) else -1

所以看进一步的要求

1. 要求找到target第一次出现的下标,如果target不存在于数组中,返回-1

如果是这种情况,就不是输出第一个大于target的元素的下标了。而是在找不到相等值的时候,直接输出-1;但是需要注意left的值大于等于len(nums)的情况,这种情况下nums[left]直接报错index out range

def bs2(nums,target):
	left,right=0,len(nums)-1
	while(left<right):
		mid=left+((right-left)>>1) #话说之前这里掉了个括号。。。
		if(target<=nums[mid]):
			right=mid
		else:
			left=mid+1
	if left<len(nums):	
		return left if nums[left]==target else -1
	else:
		return -1

这种情况下,[1,1,2,2,4,4,5],target=3; 输出是-1

2. 元素最后一次出现的下标(最大下标),不存在则返回-1
之前注释中说过判断条件target<=nums[mid]的作用是将right左移,进而mid左移,优先找先出现的元素;现在如果找最大下标,那就是需要将left右移。

def bs3(nums,target):
	left,right=0,len(nums)-1
	while(left<right):
		mid=left+((right-left)>>1) 
		if(nums[mid]<=target):
			left=mid
		else:
			right=mid-1
	if right<len(nums):	
		return right if nums[right]==target else -1
	else:
		return -1

进一步,可以统计某一个数出现的次数,即用最后一次出现的位置减去第一次出现的位置然后加一,调用上述两个函数就好,具体代码不写了。

备注
lintcode第十四题二分法,要求找第一次出现的位置,否则返回-1

def bs(nums,target):
	left,right=0,len(nums)-1
	while(left<right):
		mid=left+((right-left)>>1) #话说之前这里掉了个括号。。。
		if(target<=nums[mid]):
			right=mid
		else:
			left=mid+1
	return left if (nums[left]==target and left<len(nums)-1)else -1

当测试数据为[0,1,2,3,4,5,6]
target=6 的时候: 上述代码返回是-1,但是期望答案是6
target=7 的时候:上述代码返回是index out of range,但是期望答案是-1
然而lintcode还accepted了.
错误有两个:
第一个错误在于右边界条件,这段代码最后的条件处给的是left <len(nums)-1,这样会漏掉最后一个元素,正确的解法是上面的bs2函数,最后的判断应left<len(nums)
第二个错误是没有考虑到left大于等于len(nums)的情况,这种情况下nums[left]直接报错index out range
正确的写法应该是bs2中的写法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值