[LC] Binary Search 题目/方法总结

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/

第三境界:去掉一半

这一类博主还没有掌握也是最头疼的一类,只能通过多做题目来积累经验了;

 

原文地址:https://www.cnblogs.com/timoBlog/p/9582201.html