二分查找法
1、二分查找基本模板
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
2、while 循环的条件中是 <=,还是 <?
1. 如果right=nums.length - 1 ,则是<=,
对应的后面: left=mid+1;
right=mid-1;
2. 如果right=nums.length ,则是<
对应后面:left=mid+1;
right=mid;
3、三种类型代码对比(都是用right=nums.length-1)
//最基本的二分查找算法
int binary_search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
}
//寻找左侧边界的二分查找:(第一个大于等于target的下标)
int left_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 最后要检查 left 越界的情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
//寻找右侧边界的二分查找(第一个大于target下标-1):
/*注意如果需要查看第一个大于target小标则检查越界的时候不需要判断越界
if (nums[right] != target)
return right;
else
return right+1;
*/
int right_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定右侧边界
left = mid + 1;
}
}
// 最后要检查 right 越界的情况
if (right < 0 || nums[right] != target)
return -1;
return right;
}
//需要一个目标值的区间左右边界
class Solution {
public int[] searchRange(int[] nums, int target) {
int leftIndex=binarySearch(nums,target,true);
int rightIndex=binarySearch(nums,target,false);
if(leftIndex==-1||rightIndex == -1){
return new int[]{-1,-1};
}
return new int[]{leftIndex, rightIndex};
}
public int binarySearch(int[] nums, int target, boolean flag)
{
int left = 0;
int right = nums.length - 1;
while (left<=right){
int mid =left +(right -left)/2;
if(nums[mid] > target){
right = mid -1;
}else if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] == target){
if(flag ==true){
right =mid -1;
}else{
left =mid + 1;
}
}
}
//为true为左边界,检查左边界left是否越界
if(flag == true){
if(left>nums.length-1 || nums[left]!= target){
return -1;
}else{
return left;
}
}else{//为false为有边界,检查右边边界right是否越界
if(right<0 || nums[right]!= target){
return -1;
}else{
return right;
}
}
}
}
4、二分法查找一个区间的数值、查找第一个大于某个元素的位置
例如[1,4]:(0-1]返回下标0,
(1,4].则返回下标1
private int binarySearch(int x) {
int low = 0, high = pre.length - 1;
while (low < high) {
int mid = (high - low) / 2 + low;
if (pre[mid] < x) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
5、有序数组旋转后的二分查找
将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。
这启示我们可以在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if (n == 0) {
return -1;
}
if (n == 1) {
return nums[0] == target ? 0 : -1;
}
int l = 0, r = n - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[0] <= nums[mid]) {//和num[0]比较,从而得知哪边是有序的
//一定要加等号,因为mid是向下取整的,所以可能会和nums[0]重合相等,
// 这种情况下,当右边只有一个值就会出现错误(对于只有两个数的数组的情况会出现错误
if (nums[0] <= target && target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
} else {
if (nums[mid] < target && target <= nums[n - 1]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
}
return -1;
}
}
3、峰值问题,数值不重复其实等价于求最大值
class Solution {
//虽然数组无序,但是可以通过查找最大值得概念来使用二分查找
public int findPeakElement(int[] nums) {
int left = 0;
int right = nums.length-1;
while(left<=right){
int mid = left +(right-left)/2;
if(compare(nums,mid,mid+1)>0 && compare(nums,mid-1,mid)<0){
return mid;
}else if(compare(nums,mid,mid+1)<0){
left=mid+1;
}else if(compare(nums,mid-1,mid)>0){
right=mid-1;
}
}
return -1;
}