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