前言
不是只有数组有序才可以使用二分查找的,只要满足二段性即可
二段性:可以取一个点,然后这个点可以将数组按照题目要求划分成两个部分,然后可以直接过滤掉其中一部分,紧接着在剩下的一部分中继续使用二段性
代码模板【不要死记,理解原理】
朴素二分模板
简单,但是有局限性,只能解决部分题目【不存在重复值】
while(left <= right) {
int mid = left + ((right-left)>>>1);
if(…………) {
right = mid-1;
} else if(…………) {
left = mid+1;
} else {
return …………;
}
};
查找左边界的二分模板
左右边界的查找思路基本一样,就是处理边界不一样
while (left < right) {
int mid = left + (right-left)/2;
if(…………) {
left = mid + 1;
} else {
right = mid;
}
}
查找右边界的二分模板
while (left < right) {
int mid = left + (right-left + 1)/2;
if(…………) {
left = mid;
} else {
right = mid - 1;
}
}
704. 二分查找
题目
思路
- 暴力 O(N)
- 二分查找 O(logN)
代码
public int search(int[] nums, int target) {
int n = nums.length;
int left = 0, right = n-1;
while(left <= right) {
int mid = left + ((right-left)>>>1);
if(nums[mid] > target) {
right = mid-1;
} else if(nums[mid] < target) {
left = mid+1;
} else {
return mid;
}
}
return -1;
}
34. 在排序数组中查找元素的第一个和最后一个位置
题目
思路
- 暴力
- 二分查找左右端点 - 见模板推导
代码
public int[] searchRange(int[] nums, int target) {
int[] ret = new int[2];
ret[0] = ret[1] = -1;
// 处理边界
if(nums.length == 0) return ret;
// 二分左端点
int left = 0, right = nums.length-1;
while (left < right) {
int mid = left + (right-left)/2;
if(nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
// 处理细节 - 不包含情况
if(nums[left] != target) {
return ret;
}
ret[0] = left;
// 找右端点
right = nums.length-1;
while (left < right) {
int mid = left + (right-left+1)/2;
if(nums[mid] <= target) {
left = mid;
} else {
right = mid - 1;
}
}
ret[1] = left;
return ret;
}
69. x 的平方根
题目
思路
- 暴力
- 二分
代码
public int mySqrt(int x) {
if(x < 1) {
return 0;
}
long left = 1, right = x;
while (left < right) {
long mid = left + (right - left + 1) / 2;
if(mid * mid <= x) {
left = mid;
} else {
right = mid - 1;
}
}
return (int)left;
}
35. 搜索插入位置
题目
解析
代码
public int searchInsert(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 {
right = mid;
}
}
if(target > nums[nums.length-1]) {
return left + 1;
}
return left;
}
852. 山脉数组的峰顶索引
题目
解析
代码
public int peakIndexInMountainArray(int[] arr) {
int left = 0, right = arr.length-1;
while(left < right) {
int mid = left + (right - left) / 2;
if(arr[mid] < arr[mid+1]) {
left = mid+1;
} else {
right = mid;
}
}
return left;
}
public int peakIndexInMountainArray1(int[] arr) {
int left = 0, right = arr.length-1;
while(left < right) {
int mid = left + (right - left + 1) / 2;
if(arr[mid] > arr[mid-1]) {
left = mid;
} else {
right = mid - 1;
}
}
return left;
}
162. 寻找峰值
题目
解析
代码
public int findPeakElement(int[] nums) {
int left = 0, right = nums.length-1;
while(left < right) {
int mid = left + (right - left + 1) / 2;
if(nums[mid] > nums[mid-1]) {
left = mid;
} else {
right = mid - 1;
}
}
return left;
}
LCR 173. 点名
题目
解析
方法很多,基本都是O(N),但是二分可以达到O(logN)
- 遍历
- 按位与
- 高斯求和
- 哈希表
- 二分查找
代码
public int missingNumber(int[] nums) {
int left = 0, right = nums.length-1;
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] == mid) {
left = mid+1;
} else {
right = mid;
}
}
if(left == nums[nums.length - 1]) {
return left + 1;
}
return left;
}
153. 寻找旋转排序数组中的最小值
题目
解析
代码
// 比如【1,2】【2,1】 都是在if来处理的
public int findMin(int[] nums) {
int left = 0, right = nums.length-1;
int x = nums[0];
while (left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] >= x) {
left = mid + 1;
} else {
right = mid;
}
}
// 旋转后仍然有序,特殊处理
if(x < nums[left]) {
return x;
}
return nums[left];
}
public int findMin1(int[] nums) {
int left = 0, right = nums.length-1;
int x = nums[right];
while (left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] > x) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
}