【leetcode-Python】- 二分搜索 - 153 Find Minimum in Rotated Sorted Array

目录

 

题目链接

题目描述

示例

解题思路一

解题思路一Python实现

二分法模板

解题思路二


题目链接

153. Find Minimum in Rotated Sorted Array

题目描述

一个递增数组以数组某个未知位置为枢纽进行旋转(具体表现为,将数组最开始的若干个元素(可以为0个)搬到数组末尾),输出旋转数组的最小元素。

示例

输入:nums = [3,4,5,1,2]

输出:1

输入:nums = [4,5,6,7,0,1,2]

输出:0

输入:nums = [11,13,15,17]

输出:11

解题思路一

如果遍历数组寻找最小值的话时间复杂度为O(N)。

利用二分搜索算法寻找最小值,时间复杂度能降到O(logN)。二分搜索的应用范围不仅限于排序后的序列问题,只要问题能够通过某个binary dicision一步步缩小搜索范围即可应用。在本题中,nums中的数字在旋转后可以分为两部分,一部分都比nums最后一个元素大(可称为左排序数组),另一部分都小于或等于nums最后一个元素值(可称为右排序数组)。举个例子,旋转后的数组 [4,5,6,7,0,1,2]中,[4,5,6,7]这个升序序列属于一部分,且都比2大;[0,1,2]这个升序序列属于另一部分,均小于等于2。因此,可以将寻找旋转数组最小值的问题转化为寻找数组中第一个比nums[-1]小的值的问题。如果nums[0] < nums[-1],说明数组没有经过旋转,最小值是nums[0],这种条件下直接return nums[0]即可。

在实现时,设置target_index来保存数组中第一个比nums[-1]小的值的索引,在进行二分搜索时,应该如何缩小搜索范围呢?初始化 l = 0,r = len(nums) - 1,mid = (l + r)/2。当nums[mid]比nums[-1]大时,说明nums[mid]属于左边部分,那么就要把nums[mid]和nums[mid]左边的所有值都从当前搜索范围中去除(l = mid + 1)。如果nums[mid] \leqnums[-1],说明nums[mid]在右边部分,nums[mid]有可能是第一个比nums[-1]小的值,也可能是第n个比nums[-1]小的值,用一个target_index变量来暂存mid的值,并将nums[mid]及右边的所有值都从当前搜索范围内去除(r = mid - 1)。当r<l时跳出循环。如下图所示:

 

需要注意的是,只要当前循环 l \leq r,就有mid\leq tmp_index,因为搜索范围一直在缩小,当前循环的二分位置索引(mid)一定比上一层循环的二分位置索引(用tmp_index来存储)要小。

第一种实现方式中目标值的搜索范围为左闭右闭区间[l,r],因此循环停止条件为 l<=r,并且在特定条件下更新时为left = mid + 1,right = mid -1。但是这种实现不适用于数组中有重复元素的情况。

解题思路一Python实现

class Solution:
    def findMin(self, nums: List[int]) -> int:
        if(nums[-1]>nums[0]): 
            return nums[0]
        #问题转变为找出第一个比nums[-1]小的数
        tmp_index = len(nums)-1
        l = 0
        r = len(nums)-1
        while(l <= r):
            mid = (l + r)//2
            if(nums[mid]> nums[-1]): #如果当前项属于第一部分
                l = mid + 1 #当前项以及当前项左边的所有项都不再考虑
            else: #mid一定是小于或等于当前的tmp_index
                tmp_index = mid
                r = mid -1
        return nums[tmp_index]                    
                

二分法模板

参考知乎回答中用户Jason Li给的二分法模板和讲解,解决寻找非降序数组中第一个不小于value的值的问题。在该模板中,left初始化为0,right初始化为len(nums)。将目标值的搜索范围限定在左闭右开区间[left,right)中,跳出循环的条件为 left == right,因为此时[left,right)区间为空。在特定条件下更新right索引的方法为right = mid。循环跳出后返回nums[left]或者nums[right]都可以,因为此时left和right是重合的。在搜索过程中nums的索引被分为三部分:[0,left)左闭右开区间、[left,right)左闭右开区间、[right,len(nums))左闭右开区间。其中[first,left)范围内的元素都小于value,[right,last)范围内的元素都大于等于value。

当nums[mid]小于value时,nums[mid]理应在[first,left)这一段元素都小于value的区间范围内,因此需要令left = mid + 1扩充[first,left)区间;

当nums[mid]大于等于value时,nums[mid]理应在[right,last)这一段元素都大于等于value的区间范围内,因此需要令right = mid 来扩充[right,last)区间。

等到循环结束,left = right时,nums中第一个不小于value的值应该是[right,last)区间范围的第一个值,也就是nums[right],由于此时right = left,因此也可以返回nums[left]。(这样写省了很多麻烦,比如纠结返回left还是right的问题)

下面的模板适合直接处理在nums中求下界的问题:

def binarySearch(nums,left,right,value): #返回nums中第一个不小于value的数值
    while(left < right):#[left,right)不为空
        mid = (left + right) // 2 #Python使用长整型,不会有溢出风险。其他语言可以用mid = left + (right - left) //2 来得到中间位置的索引
        if(nums[mid] < value): #在这种条件下,要寻找的nums中第一个不小于value的数值在mid的右边
            left = mid + 1
        else:
            right = mid 
        
    return nums[left] 
                

如果改为求上界(求nums中满足x < value的最大x),可以用求下界的函数求出下界再减去1,因为x \geq value的下界减去1就是x < value的上界。同样的,x > value的下界减去1就是x \leq value的上界。如下图所示:

此求下界的模板方法同样适用于nums中有重复元素的情况和找不到满足条件的元素的情况,可以记住这种写法。

nums中有重复元素: 在此模板方法下,[0,left)左闭右开区间和[right,len(nums))左闭右开区间被不断扩充,扩充到left = right时,right和left所指的位置是第一个大于等于value的元素。即便有重复元素,但是mid应该属于[0,left)区间还是[right,len(nums))区间只根据nums[mid]和value的大小决定。不会存在无法划分的情况。而在154 Find Minimum in Rotated Sorted Array II问题中,旋转数组中可能有重复元素,当nums[mid] = nums[right]时,无法确定mid属于左排序序列还是右排序序列,因此需要进一步处理,详见【leetcode-Python】- 二分搜索 - 154 Find Minimum in Rotated Sorted Array II

nums中找不到满足条件的元素:在这种情况下nums中所有元素都小于value,left在循环中不断向右移动(left = mid + 1)直到和right重合,跳出循环后,left = right = len(nums),再根据题目的不同要求返回具体数值。如题目要求找不到满足条件的元素则返回-1, 跳出循环后的return语句就可以写成 return left if left<len(nums)  else -1。

因此给出寻找旋转数组中最小值问题的另一种实现方式(推荐),不断扩展左排序数组(大于nums[right])和右排序数组(小于等于nums[right])的范围。到left = right时循环跳出,此时两个指针都指向右排序序列的第一个元素

class Solution:
    def findMin(self, nums: List[int]) -> int:
        left = 0
        right = len(nums)-1
        while(left<right): #[left,right)不为空,保证旋转点在[left,right)范围内
            mid = left + (right - left) // 2
            if(nums[mid] > nums[right]):  #说明mid在左排序数组
                left = mid + 1
            else:
                right = mid #如果nums[mid] <= nums[right],说明mid和right都在右排序数组
        return nums[left]
                
                
                
               
                
                

 

解题思路二

其实Python可以直接利用min()函数来返回list中的最小元素。

class Solution:
    def findMin(self, nums: List[int]) -> int:
        return min(nums) 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值