二分查找最主要的是解决边界问题,根据不同的边界条件可以分为以下两个模板
class BinarySearch { //模板一,区间划分[left,mid],[mid+1,right] public int binarySearch1 (int[] nums, int target) { int left = 0, right = nums.length -1; while (left < right) { int mid = left + right >> 1; //自定义check函数 if (check(mid)) { right = mid; }else { left = mid + 1; } } return left; } //模板二,区间划分[left,mid-1],[mid,right] public int binarySearch2 (int[] nums, int target) { int left = 0, right = nums.length -1; while (left < right) { int mid = left + right + 1 >> 1; //自定义check函数 if (check(mid)) { left = mid; }else { right = mid - 1; } } return left; } }
那么这两个模板如何选择呢,其实就是看每次缩小区间的时候是left = mid 还是 right = mid,left = mid就选择模板二,right = mid就选择模板一。需要注意的是如果使用模板二,计算mid值的时候需要left + right + 1 >> 1,防止出现死循环。
例如leetcode34题,排序数组中查找元素第一个和最后一个的位置
查找元素的第一个位置,可以把区间一分为二,使得target右边所有的数据都大于等于target,区间就可以划分为[left,mid],[mid+1,right],这时候就可以选择第一个模板
查找元素的最后一个位置,可以把区间一分为二,使得target左边所有的数据都小于等于target,区间就可以划分为[left,mid-1],[mid,right],这时候就可以选择第二个模板
class Solution { public int[] searchRange(int[] nums, int target) { if (nums.length == 0) return new int[]{-1, -1}; int left = 0, right = nums.length - 1; //计算第一个出现的位置,模板一 while (left < right) { int mid = left + right >> 1; if (nums[mid] >= target) { right = mid; } else { left = mid + 1; } } //第一个都没找到说明元素不存在 if (nums[left] != target) { return new int[]{-1, -1}; } int l = left; //计算最后出现的位置,模板二 left = 0;right = nums.length - 1; while (left < right) { int mid = left + right + 1 >> 1; if (nums[mid] <= target) { left = mid; } else { right = mid - 1; } } return new int[]{l, left}; } }