1 class Solution { 2 public: 3 int searchInsert(int A[], int n, int target) { 4 int left=0,right=n-1; 5 while(left<=right) 6 { 7 int mid=left+(right-left)/2; 8 if(A[mid]==target) return mid; 9 if(A[mid]<target) 10 left=mid+1; 11 else right=mid-1; 12 } 13 return left; 14 } 15 };
当循环结束时,如果没有找到目标元素,那么left一定停在恰好比目标大的元素的index上,right一定停在恰好比目标小的index上.
特殊点的例子:当在{1,2,3,4,5}里执行二分查找找0时,此时的left=0, right=-1.(left和right也是挺拼的,为了比target大/小,不惜越界。)
这个规律非常有用,合理利用left和right在未找到target的情况下退出while循环时的特性,能解决很多问题,尤其体现在实现upperBound和lowerBound函数时。如果要利用left或right,需要保证target不在当前区间内,这样才能让while循环以未找到target的状态退出。为了实现这一点,我们需要在即使发现target==A[mid]的情况下仍然假装没看见,继续缩小搜索范围。
因此如果要在二分搜索的基础上计算bound的话,其实就是怎么处理那几个值为target的元素的问题。也就是是否把这部分值恰好为target的元素纳入下一轮搜索范围。(二分搜索本质上就是不断缩小搜索范围)。
以upperBound为例。upperBound函数是找[left,right]内第一个大于target的元素下标------所以值为target的元素肯定要排除在搜索范围之外。这样当A[mid]==target时我们仍将其排除在搜索范围外,即让low=mid+1。一直到搜索范围里已经没有值为target的元素了,此时根据二分查找的性质,当区间里没有找到target时,while循环退出后low指向刚好大于target的位置,high指向刚好小于target的位置。所以此时我们返回low即为刚好大于target的位置,即——第一个大于target的位置。
lowerBound同理。lowerBound是寻找[left,right]内第一个值不小于target的元素下标。“不小于”target的不好求,但刚好小于target的那个元素位置比较好求,因为这就是right的返回值。所以我们按照上述思路,在遇到A[mid]==target的情况下仍假装看不见,继续缩小搜索范围,直到当前范围里没有了target,最终循环退出时right就指向值刚好小于target的元素位置。而(right+1)即为不小于target的元素下标。
lowerBound和upperBound的实现代码在下一题代码中。
What if there are duplicates?
1 class Solution { 2 public: 3 vector<int> searchRange(int A[], int n, int target) { 4 int left=0,right=n-1; 5 vector<int> res(2,-1);//[-1,-1]是默认值 6 while(left<=right) 7 { 8 int mid=left+(right-left)/2; 9 if(A[mid]<target) 10 left=mid+1; 11 else if(A[mid]>target) 12 right=mid-1; 13 else 14 { 15 res[0]=lowerBound(A,left,mid,target); 16 res[1]=upperBound(A,mid,right,target)-1;//别忘了减1 17 return res; 18 } 19 } 20 return res;//这一句别忘了。当查找失败时返回[-1,-1]. 21 } 22 private: 23 int upperBound(int A[], int left, int right, int target) 24 { 25 int low = left, high = right; 26 while (low <= high) 27 { 28 int mid = low + (high - low) / 2; 29 if (A[mid] <= target) 30 low = mid + 1; 31 else 32 high = mid - 1; 33 } 34 return low; 35 } 36 int lowerBound(int A[], int left, int right, int target) 37 { 38 int low = left, high = right; 39 while (low <= high) 40 { 41 int mid = low + (high - low) / 2; 42 if (A[mid] >= target) 43 high = mid - 1; 44 else 45 low = mid + 1; 46 } 47 return high + 1; 48 } 49 };
lowerBound和upperBound与binarySearch之间的关系上一题里已经分析过了。
注意几个小细节,已在注释中标明。细节决定成败,bug-free需要谨小慎微。
3. Search in Rotated Sorted Array
1 class Solution { 2 public: 3 int search(int A[], int n, int target) { 4 int left=0,right=n-1; 5 while(left<=right) 6 { 7 int mid=left+(right-left)/2; 8 if(A[mid]==target) return mid; 9 if(A[mid]<A[right])//说明右半段是有序的 10 { 11 if(target>A[mid]&&target<=A[right]) 12 left=mid+1; 13 else 14 right=mid-1; 15 } 16 else 17 { 18 if(target>=A[left]&&target<A[mid]) 19 right=mid-1; 20 else 21 left=mid+1; 22 } 23 } 24 return -1; 25 } 26 };
最关键的把握这个规律:"总有一半是有序的,而且和另一半无区间重叠"。code ganker的总结很好。
4. Search in Rotated Sorted Array II
1 class Solution { 2 public: 3 bool search(int A[], int n, int target) { 4 for(int i=0;i<n;i++) 5 if(A[i]==target) return true; 6 return false; 7 } 8 };
由于允许有duplicates,会导致没有办法像I中那样根据A[mid]和A[left]、A[right]的比较来确定是哪一半有序,应该在哪一半查找。
导致最坏时间复杂度变为O(n)。因此用最简单的遍历来实现就可以。
1 class Solution { 2 public: 3 bool searchMatrix(vector<vector<int> > &matrix, int target) { 4 if(matrix.size()==0||matrix[0].size()==0) return false; 5 int m=matrix.size(); int n=matrix[0].size(); 6 int left=0,right=m*n-1; 7 while(left<=right) 8 { 9 int mid=left+(right-left)/2; 10 int midX=mid/n; int midY=mid%n;//注意:这里是n,不是m! 11 if(matrix[midX][midY]==target) return true; 12 if(matrix[midX][midY]<target) 13 left=mid+1; 14 else 15 right=mid-1; 16 } 17 return false; 18 } 19 };
6. sqrt(x)
1 class Solution { 2 public: 3 int sqrt(int x) { 4 if(x<2) return x; 5 int left=1,right=x/2+1; 6 while(left<=right) 7 { 8 int mid=left+(right-left)/2; 9 if(mid<=x/mid&&(mid+1)>x/(mid+1))//防止溢出 10 return mid; 11 if(mid>x/(mid)) 12 right=mid-1; 13 else 14 left=mid+1; 15 } 16 return -1; 17 } 18 };
比较蹊跷的是if(mid>x/mid)和下面的else不能换位置。尚不知为何。