本文始发于我的公众号:TechFlow,原创不易,求个关注web
今天是LeetCode专题第50篇文章,咱们来聊聊LeetCode中的81题Search in Rotated Sorted ArrayII。算法
它的官方难度是Medium,点赞1251,反对470,经过率32.8%。从经过率上来看,这题属于Medium难度当中偏难一些的题目,也的确如此,稍稍有些考验思惟。数组
假设咱们有一个含有重复元素的有序数组,咱们随意选择一个位置将它分红两半,而后将这两个部分调换顺序拼接成一个新的数组。如今给定一个target,要求返回一个bool结果,代表target是否在数组当中。编辑器
Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true
Input: nums = [2,5,6,0,0,1,2], target = 3
Output: false
若是是你按照顺序刷LeetCode或者是本专题的话,你会发现咱们在以前作过一道很是类似的题目。它就是LeetCode的33题,Search in Rotated Sorted ArrayI。不过不一样的是,在33题的题意当中,明确代表了数组当中的元素是不包含重复元素的,除此以外,这两题的题意彻底同样。url
LeetCode 33,在不知足二分的数组内使用二分的方法spa
这么一点小小的差异会带来解法的变化吗?3d
答案固然是确定的,否则出题人能够退休了。code
问题是,问题出在哪里呢?blog
咱们先不着急,先来回忆一下33题中的作法。咱们当时使用了一个最简单的笨办法,就是先经过二分法找到数组截断的位置。而后再经过截断的位置还原出原数组的状况,根据咱们target的大小,找到它可能存在的位置。递归
可是在当前这个问题当中,这个思路走不通了。走不通的缘由也很简单,就是由于重复元素的存在。
举个例子:[1, 3, 1, 1, 1, 1, 1, 1]
当咱们进行二分查找的时候,发现mid是1和left的1相等,咱们根本没法判断截断点究竟在mid的左侧仍是右侧,二分查找也就无从谈起了。
咱们固然能够退一步采用遍历的方法去寻找切分点,可是既然如此,咱们为何不直接去寻找答案呢?反正都已是O(n)的复杂度了。因此这是行不通的,咱们想要使得复杂度维持在就必需要寻找其余的路数。
思路和解法不少时候不是凭空来的,须要咱们对问题进行深刻的分析。在这个问题当中,咱们的问题是明确而且简单的。就是一个调换了部分顺序的有序数组,只是咱们不肯定的是调换的部分究竟有多长。因为咱们最终但愿经过二分法来寻找答案,因此咱们能够根据调换的元素是否过半想出两种状况来。
我把这两种状况用图展现出来:
也就是说咱们的分割点可能在数组的前半段也可能在后半段,对于这两种状况咱们的处理方法是不一样的。
咱们先看第一种状况,数组的前半段是有序的,后半段存在截断。若是target的范围在前半段当中,咱们能够抛弃掉后半段,直接在前半段中进行二分。不然,咱们须要舍弃前半段,在后半段当中重复这个过程。咱们能够把后半段当作是一个全新的问题,也同样能够分红两种状况,相似于递归同样的往下执行便可。
再来看第二种状况,第二种状况的后半段和第一种状况的前半段是同样的,都是有序的元素,咱们直接二分便可。它的前半段和第一种状况的后半段是同样的,咱们无法判断,须要继续二分。
也就是说,咱们只能在有序的数组进行二分,若是当前数组存在分段,不是总体有序的,那咱们就对它进行拆分。拆分以后总能找到有序的部分,若是还找不到就继续拆分。由于分段点只有一个,因此不论当前的数组什么样,拆分一次以后,必然至少能够找到一段是有序的。
想明白这点以后就简单了,看起来很像是递归,但实际上它的本质仍然是二分。代码并不难写,可是还有一个问题没解决,就是当nums[m] = nums[l]的时候,咱们如何判断是哪种状况呢?
答案是无法判断,两种状况都有可能,对于这种状况也没有很好的办法,我想出来的办法是能够将l向右移动一位,至关于抛弃了一个最左侧的数。咱们把这些思路总结总结,代码也就出来了:
class Solution:
def search(self, nums: List[int], target: int) -> bool:
l, r = 0, len(nums)-1
while l <= r:
m = (l + r) >> 1
if nums[m] == target:
return True
if nums[l] == nums[m]:
l += 1
continue
if nums[l] < nums[m]:
if nums[l] <= target < nums[m]:
r = m - 1
else:
l = m + 1
else:
if nums[m] < target <= nums[r]:
l = m + 1
else:
r = m - 1
return False
到这里,咱们关于这道题的题解就结束了。在问题的最后,出题人给咱们留了一个问题,和33题比起来,这题的解法的时间复杂度会有变化吗?
表面上看咱们同样用到了二分,因此一样是log级的复杂度,问题的复杂度并无变化。但实际上并非这样的,咱们来看一种最坏的状况,假设数组当中全部的值所有相等。这个时候二分就不起效果了,最终会退化成O(n)的线性枚举,这样又变成了O(n)的复杂度。固然,在大部分状况下,这并不会发生。因此是算法的最坏复杂度退化成了O(n),平均复杂度依然是O(logN)。
若是喜欢本文,能够的话,请点个关注,给我一点鼓励,也方便获取更多文章。
本文使用 mdnice 排版