https://leetcode.com/problems/search-in-rotated-sorted-array-ii/
Follow up for "Search in Rotated Sorted Array":
What if duplicates are allowed?
Would this affect the run-time complexity? How and why?
Write a function to determine if a given target is in the array.
解题思路:
这题与上一题Search in Rotated Sorted Array略有不同,数组中允许重复元素。粗粗一想,不太容易判断重复元素会给我们带来什么样的影响,还有到底重复元素出现在什么地方,会给我们带来关键性的影响?这是这道题的两个关键问题。那么我们可以先把上一道题的代码塞进去跑看看。
结果出来有错误,
Input: | [1,3,1,1,1], 3 |
Output: | false |
Expected: | true |
原来当A[mid] == A[start]的时候,原来可以放在第二种情况里,也就是2 4 5 6 7 0 1这种情况。但是现在由于有重复元素,我们不能判断到底属于哪种情况了。
其实呢?肉眼可以看出来,上面的例子里,A[mid] == A[start]的时候,如果mid往前有>A[mid]的时候,就可以认为是A[start] > a[mid]的情况,放在这里处理。所以下面的代码写了另外的一个方法,如果A[mid] == A[start],就从start到mid往后一个个看,出现>A[mid]的元素,就当作是A[start] > a[mid]的情况。
public class Solution { public boolean search(int[] A, int target) { int start = 0; int end = A.length - 1; while(start <= end){ int mid = (start + end) / 2; if(target == A[mid]){ return true; } if(A[start] > A[mid] || hasBigger(A, start, mid, A[mid])){ //后半段一定比mid大,所以这时target一定在前面 if(target < A[mid]){ end = mid - 1; } //有可能在后半段,也有可能在rotate到前面的那部分 if(target > A[mid]){ if(target <= A[end]){ start = mid + 1; }else{ end = mid - 1; } } }else{ //前半段一定比mid小,所以这时target一定在后面 if(target > A[mid]){ start = mid + 1; } if(target < A[mid]){ if(target < A[start]){ start = mid + 1; }else{ end = mid - 1; } } } } return false; } public boolean hasBigger(int[] A, int start, int end, int target){ for(int i = start; i < end; i++){ if(A[i] > target){ return true; } } return false; } }
后来从网上看到一个更为简单的方法,不用额外写判断条件了。当A[mid] == A[start]的时候,start++再判断,重新定义二分搜索的范围,就可以了。整个代码要简洁很多。
public class Solution { public boolean search(int[] A, int target) { int start = 0; int end = A.length - 1; while(start <= end){ int mid = (start + end) / 2; if(target == A[mid]){ return true; } if(A[start] > A[mid]){ //后半段一定比mid大,所以这时target一定在前面 if(target < A[mid]){ end = mid - 1; } //有可能在后半段,也有可能在rotate到前面的那部分 if(target > A[mid]){ if(target <= A[end]){ start = mid + 1; }else{ end = mid - 1; } } }else if(A[start] < A[mid]){ //前半段一定比mid小,所以这时target一定在后面 if(target > A[mid]){ start = mid + 1; } if(target < A[mid]){ if(target < A[start]){ start = mid + 1; }else{ end = mid - 1; } } }else if(A[start] == A[mid]){ start++; } } return false; } }
上面两个方法,时间复杂度在最差的时候,都是O(n),比如在111111111151,搜索5。两种方法都要花大量时间去在前半段找大于1的数字。
另外一个问题,根据循环不变式loop invariant的原理,在二分搜索循环的内部如果更新了start和end,就必须立刻进入下一次循环,不能再对新的start或者end进行判断、返回了。否则会破坏循环不变的性质。
比如第二种方法start++后就不能在本次循环内再处理了,必须进入下次循环。妄想多次更新start,避免多次循环,会破坏循环的性质。
// 2018/09/16
class Solution { public boolean search(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = (left + right) / 2; if (nums[mid] == target) { return true; } if (nums[left] == nums[mid]) { left++; continue; } if (nums[left] < nums[mid]) { if (target >= nums[left] && target <= nums[mid]) { right = mid - 1; } else { left = mid + 1; } } if (nums[right] == nums[mid]) { right--; continue; } if (nums[mid] < nums[right]) { if (target >= nums[mid] && target <= nums[right]) { left = mid + 1; } else { right = mid - 1; } } } return false; } }