二分查找 个人详解 力扣#34 #74 #33 #81

 1 int binarySearch(int[] nums, int target) {
 2     int left = 0, right = ...;
 3 
 4     while(...) {
 5         int mid = (right + left) / 2;
 6         if (nums[mid] == target) {
 7             ...
 8         } else if (nums[mid] < target) {
 9             left = ...
10         } else if (nums[mid] > target) {
11             right = ...
12         }
13     }
14     return ...;
15 }

首先是二分查找的框架 其中...的是需要判断的地方

 

基础情况一:

 1 int binarySearch(int[] nums, int target) {
 2     int left = 0; 
 3     int right = nums.length - 1; // 注意
 4 
 5     while(left <= right) { // 注意
 6         int mid = (right + left) / 2;
 7         if(nums[mid] == target)
 8             return mid; 
 9         else if (nums[mid] < target)
10             left = mid + 1; // 注意
11         else if (nums[mid] > target)
12             right = mid - 1; // 注意
13         }
14     return -1;
15 }

首先 为什么while循环中的...是left <= right

题目中的right 是num.length - 1 等价于[left, right]

如果right 为num.length呢 则等价于[left, right) => 数组越界啦

 

left <= right 等价于[right + 1, right] 找个数带入进去也就是[3, 2]

如果是left < right 则等价于[right, right] 也就是[2, 2]

这种情况很可能在[1, 2]的时候更新了搜索区间 直接return -1跳出了

而mid = 2的情况还没来得及搜索 从而产生错误

 

来一个进阶的题目 其实就是昨天发的力扣#34

里面有两个延伸情况 一个是查找左侧区间 一个是查找右侧区间

 1 class Solution {
 2     public int[] searchRange(int[] nums, int target) {
 3         int left = 0, right = nums.length - 1;
 4         int[] res = {-1, -1};
 5         while(left <= right){
 6             //搜索区间[left, right]都可以取到
 7             int mid = (left + right) / 2;
 8             //为什么left 和 right 更新之后都是mid +- 1呢
 9             //因为判断条件是mid 也就是说mid已经被判断过了
10             //所以我们可以直接进入mid周围的下一个区间继续判断
11             if(nums[mid] < target){
12                 left = mid + 1;
13             //这里是寻找左区间 退出的条件是[left, left - 1] 可自行理解
14             }else if(nums[mid] > target){
15                 right = mid - 1;
16             }else{
17                 right = mid - 1;
18             }
19         }
20         if(left == nums.length || nums[left] != target){
21             return res;
22         }else{
23             res[0] = left;
24         }
25         //第二次扫描
26         left = 0;
27         right = nums.length - 1;
28         while(left <= right){
29             int mid = (left + right) / 2;
30             //这里同上 不过退出的条件是[right + 1, right]
31             if(nums[mid] < target){
32                 left = mid + 1;
33             }else if(nums[mid] > target){
34                 right = mid - 1;
35             }else{
36                 left = mid + 1;
37             }
38         }
39         res[1] = right;
40         return res;
41     }
42 }

再来个进阶题目咯 力扣#74 要注意两个特殊判断

第一次对二维矩阵的第一列进行二分查找 要注意理解为什么搜索完第一列后

返回的是binarySearch(matrix[left - 1], target);

 1 class Solution {
 2     public boolean searchMatrix(int[][] matrix, int target) {
 3         int left = 0, right = matrix.length - 1;
 4         if(matrix.length == 0) return false;
 5         if(matrix[0].length == 0) return false;
 6         while(left <= right){
 7             int mid = (left + right) / 2;
 8             if(matrix[mid][0] == target){
 9                 return true;
10             }else if(matrix[mid][0] < target){
11                 left = mid + 1;
12             }else if(matrix[mid][0] > target){
13                 right = mid - 1;
14             }
15         }
16         if(left - 1 <= 0) return binarySearch(matrix[0], target);
17         return binarySearch(matrix[left - 1], target);
18     }
19 
20     public boolean binarySearch(int[] matrix, int target){
21         int left = 0, right = matrix.length - 1;
22         while(left <= right){
23             int mid = (left + right) / 2;
24             if(matrix[mid] == target){
25                 return true;
26             }else if(matrix[mid] < target){
27                 left = mid + 1;
28             }else if(matrix[mid] > target){
29                 right = mid - 1;
30             }
31         }
32         return false;
33     }
34 }

力扣 #33和他的后续 #81 主要是旋转排序数组 可以通过mid 和 left判断 target在哪个区间 然后确定下一步的二分查找 比如 [ 4 5 6 7 1 2 3]  从 7 劈开 左边是 [ 4 5 6 7] 右边是 [ 7 1 2 3] 左边是有序的

 1 class Solution {
 2     public int search(int[] nums, int target) {
 3         int left = 0, right = nums.length - 1;
 4         while(left <= right){
 5             int mid = (left + right) / 2;
 6             if(target == nums[mid]){
 7                 return mid;
 8             }
 9             if(nums[left] <= nums[mid]){ //左半段是有序的
10                 if(target >= nums[left] && target < nums[mid]){//target在这一段里
11                     right = mid - 1;
12                 }else{//target在另一段里
13                     left = mid + 1;
14                 }
15             }else{//右半段是有序的 下面同理
16                 if(target <= nums[right] && target > nums[mid]){
17                     left = mid + 1;
18                 }else{
19                     right = mid - 1;
20                 }
21             }
22         }
23         return -1;
24     }
25 }

这一题多了个特例 因为允许出现重复数字 当nums[left] == nums[mid]的时候 例如nums = [ 1, 3, 1, 1, 1 ]  target = 3 就会返回false 因为我们默认是认为左半段有序 但由于重复数字出现

nums[start] = 1 nums[mid] = 1 此时左半部分[1, 3, 1]并不是有序的 所以要单独考虑 nums[start] == nums[mid]的情况

 1 class Solution {
 2     public boolean search(int[] nums, int target) {
 3         int left = 0, right = nums.length - 1;
 4         while(left <= right){
 5             int mid = left + (right - left) /2;
 6             if(nums[mid] == target){
 7                 return true;
 8             }
 9             if(nums[left] < nums[mid]){
10                 if(target >= nums[left] && target < nums[mid]){
11                     right = mid - 1;
12                 }else{
13                     left = mid + 1;
14                 }
15             }else if(nums[left] == nums[mid]){
16                 left++;
17             }else{
18                 if(target > nums[mid] && target <= nums[right]){
19                     left = mid + 1;
20                 }else{
21                     right = mid - 1;
22                 }
23             }
24         }
25         return false;
26     }
27 }

 

posted @ 2020-12-02 20:37  加利亚的赤色恶魔  阅读(86)  评论(0)    收藏  举报