二分查找 个人详解 力扣#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 }