Binary search 是最简单也十分常用的一种算法,能把O(n) 的搜索降到O(logn)这个帖子的总结是参考大神Grandyang的,甚至很多地方是直接抄袭过来,原贴在这里http://www.cnblogs.com/grandyang/p/6854825.html
Binary Search Template
二分法常见关键词:sorted;
二分常见痛点有以下几个:
- 循环条件:start + 1 < end
- mid = start + (end - start) /2;
- A[mid] ==, < >
- return A[start] or A[end]
第一境界:寻找target
这个level其实就是直接套用模板,一般就是在sorted array里面找target/insertion;
例题: https://leetcode.com/problems/binary-search/description/
1 public int search(int[] A, int target) { 2 int s = 0, e = A.length - 1; 3 while (s + 1 < e){ 4 int mid = s + (e - s) / 2; 5 if (A[mid] == target){ 6 return mid; 7 }else if (A[mid] < target){ 8 s = mid + 1; 9 }else{ 10 e = mid - 1; 11 } 12 } 13 14 if (A[s] == target) return s; 15 if (A[e] == target) return e; 16 return -1; 17 }
例题:https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/description/
这个题目非常直接,直接考察找两个postion,也是十分简单,就是套用模板;可以采取套用模板两次,找到first,last;也可以先找到first,然后把指针往后移找到last;
也可以先o(logn)找到first,然后在first,end直接二分o(logm)找到last;这里选用了第二种方法,o(logn) 找到first,然后平移pointer去找last,worst case O(n);
1 public int[] searchRange(int[] A, int target) { 2 if (A.length == 0) return new int[]{-1, -1}; 3 4 int s = 0, e = A.length - 1; 5 while (s + 1 < e){ 6 int mid = s + (e - s)/ 2; 7 if (A[mid] >= target){ 8 e = mid; 9 }else{ 10 s = mid + 1; 11 } 12 } 13 14 int first = -1; 15 if (A[s] == target){ 16 first = s; 17 }else if (A[e] == target){ 18 first = e; 19 }else{ 20 return new int[]{-1, -1}; 21 } 22 23 int last = first; 24 while (last + 1 < A.length && A[last + 1] == target){ 25 last ++; 26 } 27 28 return new int[]{first, last}; 29 }
第二境界:寻找OOXX,即满足条件的最后/第一 OOOOOOO...OOXX….XXXXXX
这个level 大部分是找满足条件的一个或者最后一个,即分界处,相关常见关键词:first、last、rotated sorted、peak etc
例题:https://leetcode.com/problems/first-bad-version/description/
太简单就不贴代码了;
Search in rotated sorted array
例题:https://leetcode.com/problems/search-in-rotated-sorted-array/description/
这个题目就是画图,找到几种情况start, end该怎么变就行,主要关注的是A[end] and A[mid] 的关系。
1 public int search(int[] nums, int target) { 2 if (nums == null || nums.length == 0){ 3 return -1; 4 } 5 6 int start = 0, end = nums.length - 1; 7 while (start + 1 < end){ 8 int mid = start + (end - start) / 2; 9 if (nums[mid] == target){ 10 return mid; 11 }else if (nums[mid] < target){ 12 if (nums[end] >= nums[mid] && target > nums[end]){ 13 end = mid; 14 }else{ 15 start = mid; 16 } 17 }else{ 18 if (nums[end] < nums[mid] && target <= nums[end]){ 19 start = mid; 20 }else{ 21 end = mid; 22 } 23 } 24 } 25 26 if (nums[start] == target){ 27 return start; 28 }else if (nums[end] == target){ 29 return end; 30 } 31 32 return -1; 33 }
例题:https://leetcode.com/problems/search-in-rotated-sorted-array-ii/description/
是上面那题的变形,要注意的是有duplicate的出现,那么我们A[start] == A[end]成为可能,这样我们变换end的时候,只能让end --, 不能直接end = mid;
1 public boolean search(int[] A, int target) { 2 if (A.length == 0) return false; 3 4 int start = 0, end = A.length - 1, mid; 5 6 while (start + 1 < end){ 7 mid = start + (end - start) / 2; 8 System.out.println("mid=" + mid); 9 if (A[mid] == target) return true; 10 if (A[mid] > target){ 11 if (target <= A[end] && A[mid] > A[end]){ 12 start = mid; 13 }else{ 14 end = (A[mid] < A[end]) ? mid : end - 1; 15 } 16 }else{ // A[mid] < target 17 if (target > A[end] && A[mid] <= A[end]){ 18 end = (A[mid] < A[end]) ? mid : end - 1; 19 }else{ 20 start = mid; 21 } 22 } 23 } 24 25 if (A[start] == target || A[end] == target) return true; 26 return false; 27 }
例题:https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/
这可以转化成找第一个数where A[i] < A[i + 1];,我们可以令target = A[end];这样转化成找到first value that less than target;
1 public int findMin(int[] A) { 2 // change to find the first value that less than the A[end]; 3 int s = 0, e = A.length - 1, target = A[e]; 4 while (s + 1 < e){ 5 int mid = s + (e - s) / 2; 6 if (A[mid] >= target){ 7 s = mid; 8 }else{ 9 e = mid; 10 } 11 } 12 13 return A[s] <= target ? A[s] : A[e]; 14 15 }
https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii/description/
上一题的拓展,主要不同就是有duplicate;和search in rotated array 一样,有duplicate的时候我们不能简单end = mid或者start = mid,而是s++ 或者e++,想象一下这个case[3,1,3,3,3], 如果我们看到A[mid] >= target(3); 就赋值-> start = mid,这样会丢失掉1这个值,所以我们要的是start ++;
public int findMin(int[] A) { // change to find the first value that less than the A[end]; int s = 0, e = A.length - 1, target = A[e]; while (s + 1 < e){ int mid = s + (e - s) / 2; if (A[mid] >= target){ s ++; }else{ e = mid; } } return A[s] <= target ? A[s] : A[e]; }
自己找higher bound
例题:https://leetcode.com/problems/search-in-a-sorted-array-of-unknown-size/description/
size 不知道,也即是没有end,那我们就用一个e做expoenital increase直到找到array中大于target,就可以赋值为这个higher bound;
1 public int search(ArrayReader reader, int target) {
2 int e = 1;
3 while (reader.get(e) < target){
4 e <<= 1;
5 }
6
7 int s = e >> 1;
8 while (s + 1 < e){
9 int mid = s + (e - s) / 2;
10 if (reader.get(mid) == target){
11 return mid;
12 }else if (reader.get(mid) < target){
13 s = mid + 1;
14 }else{
15 e = mid - 1;
16 }
17 }
18
19 if (reader.get(s) == target){
20 return s;
21 }else if (reader.get(e) == target){
22 return e;
23 }
24
25 return -1;
26 }
应用实例
例题:https://leetcode.com/problems/heaters/description/
这个题目可以对每个房子找到它在heater中的位置,然后求两个最小值,然后对所有房子这个最小值求最大值即可;
其实这个题不需要用binary search;用two-pointer其实更好
1 class Solution { 2 public int findRadius(int[] houses, int[] heaters) { 3 int max = 0; 4 Arrays.sort(heaters); 5 6 // for each house, find its position in heater; 7 for (int h : houses){ 8 //int min = find(heaters, h); 9 //System.out.println("h=" + h + " min=" + min); 10 max = Math.max(max, find(heaters, h)); 11 } 12 13 return max; 14 } 15 16 private int find(int[] A, int h){ 17 int s = 0, e = A.length - 1; 18 while (s + 1 < e){ 19 int mid = s + (e - s) / 2; 20 if (A[mid] == h){ 21 return 0; 22 }else if (A[mid] < h){ 23 s = mid; 24 }else{ 25 e = mid; 26 } 27 } 28 29 return Math.min(Math.abs(h - A[s]), Math.abs(A[e] - h)); 30 } 31 }
two pointer:
1 class Solution { 2 public int findRadius(int[] houses, int[] heaters) { 3 Arrays.sort(houses); 4 Arrays.sort(heaters); 5 6 int j = 0, max = 0; 7 for (int i = 0; i < houses.length; i ++){ 8 // when the next heaters is closer to the current house, we move pointer to next heater 9 while (j + 1 < heaters.length && 10 Math.abs(heaters[j] - houses[i]) >= Math.abs(heaters[j+1] - houses[i])){ 11 j ++; 12 } 13 14 max = Math.max(max, Math.abs(heaters[j] - houses[i])); 15 } 16 17 return max; 18 } 19 }
例题: https://leetcode.com/problems/arranging-coins/description/
这是一个典型的二分数学问题,利用sum = (1 + end) * end / 2 和 n的关系我们可以看出是找最后一个满足条件的位置,二分典型oooxxx问题;
1 public int arrangeCoins(int n) { 2 long s = 0, e = n; 3 long nlong = (long) n; 4 5 while (s + 1 < e){ 6 long mid = s + (e - s) / 2; 7 8 // expected sum = (1 + mid) * mid / 2; 9 if ( (1 + mid) * mid / 2 <= nlong){ 10 s = mid; 11 }else{ 12 e = mid - 1; 13 } 14 } 15 16 if ((1 + e) * e / 2 <= nlong) return (int) e; 17 if ((1 + s) * s / 2 <= nlong) return (int) s; 18 return 0; 19 }
相似例题:
https://leetcode.com/problems/valid-perfect-square/description/
https://leetcode.com/problems/valid-triangle-number/description/
https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/
第三境界:去掉一半
这一类博主还没有掌握也是最头疼的一类,只能通过多做题目来积累经验了;