二分查找基本介绍
二分查找是一种非常非常高效的查询算法,针对的是一个有序的数据集合,每次都通过跟区间的中间元素对比,将带查找的区间缩小为之前的一半,知道找到要查找的元素,或者区间被缩小为0。
代码实现:
循环实现
public 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;
}
递归实现
public int binarySearchByRecursion(int[] arr, int num, int start, int end) {
//临界值快速判断(num小于数组左边或者大于右边,必定找不到) start > end 为递归终止条件
if (arr[start] > num || arr[end] < num || start > end) {
return - 1;
}
int mid = start + (end - start) / 2;
if (num == arr[mid]) {
return mid;
} else if (num < arr[mid]) {
return binarySearchByRecursion(arr, num, start, mid - 1);
} else {
return binarySearchByRecursion(arr, num, mid + 1, end);
}
}
需要注意的小点:
1.mid=(left+right)/2跟mid = left + (right-left) / 2的区别?
答:mid = left + (right-left) / 2 能够防止溢出问题,比如说是int类型,如果left,right都很大的话,(left+right)可能会超出Integer.MAX_VALUE,导致溢出;而后,就不存在这种情况,mid永远会小于等于right。
2.为什么 while 循环的条件中是 <=,而不是 < ?
答:因为初始化 right 的赋值是 nums.length - 1,即最后⼀个元素的索引,⽽不是 nums.length。这⼆者可能出现在不同功能的⼆分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引⼤⼩为 nums.length 是越界的。
- while(left <= right) 的终⽌条件是 left == right + 1,写成区间的形式就是
- [right +1,right],或者带个具体的数字进去 [3, 2],可⻅这时候区间为空,因为没有数字既⼤于等于 3 ⼜⼩于等于2 的吧。所以这时候while 循环终⽌是正确的,直接返回 -1 即可。
- while(left < right) 的终⽌条件是 left == right,写成区间的形式就是 [right, right],或者带个具体的数字进去 [2, 2],这时候区间⾮空,还有⼀个数 2,但此时 while 循环终⽌了。也就是说这区间[2, 2]被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。
LeetCode经典案例
378 有序矩阵中第K小的元素
题目:
第一个我们很容易想到将二维数组展开成一维,然后直接进行排序
public int kthSmallest(int[][] matrix, int k) {
int row=matrix.length;
int column =matrix[0].length;
int[] sorted=new int[row*column];
int index =0;
for(int[] out:matrix) {
for(int in:out) {
sorted[index++]=in;
}
}
Arrays.sort(sorted);
return sorted[k-1];
}
第二个方法利用二分法的思想对有序矩阵进行排序,直接上代码
/*
先找到一个中间数mid=left+(right-left)/2,
从矩阵左下角matrix[i][0]开始和mid进行比较,若小于等于mid,则往右移动,若大于mid则往上移动,并计算元素个数num。循环终止条件为:走出矩阵范围。
若num小于k,则证明第K小的元素在右半边,故令left=mid+1,right=right,继续循环。
若num大于k,则证明第K小的元素在左半边(包含mid),故令left=left,right=mid,继续循环。最终返回条件为left。
*/
public int kthSmallest(int[][] matrix, int k) {
int n = matrix.length;// 二维矩阵的行数
int left = matrix[0][0];// 二维矩阵左上角的元素(最小的元素)
int right = matrix[n-1][n-1];// 二维矩阵右下角的元素(最大的元素)
while(left < right){
int mid = left + (right - left)/2;
int count = help(matrix, mid, n);// 获得左上角中小于等于元素得个数
if(count < k){
left = mid + 1;
}else{
right = mid;
}
}
return left;
}
public int help(int[][] matrix, int mid, int n){// 参数传入数组、中间元素和数组的行长度
int i = n - 1;// 表示行
int j = 0;// 表示列
int count = 0;// 用于计数
while(i >= 0 && j < n){
if(matrix[i][j] <= mid){
j++;
count += i+1;// j这一列的元素都小,所以将元素个数统一加进去
}else{
i--;
}
}
return count;// 记录左上角中小于等于mid的元素个数
}
852 山脉数组的峰顶索引
题目:
第一种方法是根据题意想到这道题的本质是寻找这个数组中最大的数
public int peakIndexInMountainArray(int[] arr) {
int index=0;
for(int i=0;i<arr.length;i++) {
if(arr[i]>arr[i+1]) {
return i;
}
}
return -1;
}
第二种方法是采用二分查找的思想
public int peakIndexInMountainArray(int[] A) {
int l=1,n=A.length,r=n-2;
while (l<=r)//二分查找
{
int mid=(r-l)/2+l;
if(A[mid]>A[mid-1])//在山峰的左边
l=mid+1;
else if (A[mid]>A[mid+1])//在山峰的右边
r=mid-1;
}
return r;
}
1221分割平衡字符串
题目:
public int balancedStringSplit(String s) {
char[] after=s.toCharArray();
int count=0;
int num=0;
for(int i=0;i<after.length;i++) {
if(after[i]=='L') {
count++;
}else {
count--;
}
if(count==0) {
num++;
}
}
return num;
}