1. 程式人生 > 實用技巧 >二分查詢 個人詳解 力扣#34 #74 #33 #81

二分查詢 個人詳解 力扣#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 }