二分查找常常用于有序数组的查找操作。当然如果是一个问题,数组的顺序满足特定条件(不仅仅是升序或者降序,也可以是先升后降或先降后升),就可以通过逐步排查,缩小问题的规模的方式找到,这种算法也是二分查找算法。
PS:不想看过程的,直接拉到万能公式,即看即用!
两种二分查找
第一种
第二种
从上面可以看出:
第二种方式有两种边界转移方式:
left = mid + 1;
和right = mid;
这种不会产生死循环left = mid-1;
和right = mid;
这种会产生死循环(循环条件一直成立),此时需要把mid向上取整。
思维导图
万能公式
int search(int[] nums, int target, int l, int r) {
while (l < r) {
int mid = l + (r - l) / 2;
// if (符合条件) return mid;
if (target在右区间) {
l = mid + 1;
} else {
r = mid;
}
}
if (符合条件) return l;
else return -1;
}
适用于:
主要注意的几个点:
- while继续循环的条件:
left<right;
注意不要带等于号,while退出循环的时候,一定是left==right,所以不需要额外的考虑用left还是right去判断条件。 - 取中位数的时候,防止left和right过大,以至于left+right超过int类型上限,可以用
mid = left + (right - left) / 2;
来求中位数。 - 在while中,如果题目说明了没有重复元素的话,那么万能公式中注释的if条件可以去掉注释,以便提前跳出条件,如果存在相同的元素让输出最左边或者最右边的元素,那么这个if条件就不能加
- 赋值left和right边界的时候,需要注意left一定要先赋值,跟快排需要先从右边一样。
代码示例(套公式示例)
LeetCode704
【题目】
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
【示例】
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
【代码】
public int search(int[] nums, int target) {
if (nums==null || nums.length==0)
return -1;
int left,right,mid;
left = 0;
right = nums.length-1;
while (left < right) {
mid = left + (right - left) / 2;
// if (nums[mid]==target)
// return mid;
if (nums[mid]<target)
left = mid+1;
else
right = mid;
}
// 由于条件可能不存在,所以需要额外判断
if (target==nums[left])
return left;
else return -1;
}
LeetCode35
【题目】
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
【示例】
输入: [1,3,5,6], 5
输出: 2
输入: [1,3,5,6], 2
输出: 1
输入: [1,3,5,6], 0
输出: 0
输入: [1,3,5,6], 7
输出: 4
【代码】
public int searchInsert(int[] nums, int target) {
if (nums==null || nums.length==0)
return 0;
int left,right,mid;
left = 0;
right = nums.length-1;
// 最大值的情况需要额外判断
if (target>nums[right]) return right+1;
while (left < right) {
mid = left + (right - left) / 2;
// if (nums[mid]==target)
// return mid;
if (nums[mid]<target)
left = mid+1;
else
right = mid;
}
// 由于条件一定成立,所以直接输出即可
return left;
}
LeetCode1095
【题目】
给你一个 山脉数组 mountainArr,请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。
如果不存在这样的下标 index,就请返回 -1。
何为山脉数组?如果数组 A 是一个山脉数组的话,那它满足如下条件:
首先,A.length >= 3
其次,在 0 < i < A.length - 1 条件下,存在 i 使得:
- A[0] < A[1] < … A[i-1] < A[i]
- A[i] > A[i+1] > … > A[A.length - 1]
你将 不能直接访问该山脉数组,必须通过 MountainArray 接口来获取数据:
- MountainArray.get(k) - 会返回数组中索引为 k 的元素(下标从 0 开始)
- MountainArray.length() - 会返回该数组的长度
【示例】
输入:array = [1,2,3,4,5,3,1], target = 3
输出:2
解释:3 在数组中出现了两次,下标分别为 2 和 5,我们返回最小的下标 2。
输入:array = [0,1,2,4,2,1], target = 3
输出:-1
解释:3 在数组中没有出现,返回 -1。
【思考】
-
先找到山顶元素 mountaintop 所在的索引。
-
在前有序且升序数组中找 target 所在的索引,如果找到了,就返回,如果没有找到,就执行第 3 步;
-
如果步骤 2 找不到,就在后有序且降序数组中找 target 所在的索引。
所以问题的关键就是如何在一个先升后降的数组中找到最高的一个。
// 寻找峰顶的代码
public int getTop(MountainArray mountainArr) {
int left,right,mid,midVal,midAf;
left = 0;
right = mountainArr.length()-1;
while (left < right) {
mid = left + (right - left) / 2;
midVal = mountainArr.get(mid);
midAf = mountainArr.get(mid+1);
if (midAf > midVal)// 如果当前的数比右边的数小,它一定不是山顶
left = mid + 1;
else
right = mid;
}
return left;
}
如果当前的数比右边的数小,它一定不是山顶,又由于left和right的二分是mid在左区域(看上图),所以最后跳出的时候,只有两个元素,mid在左边,left和right同时指向右边,右边就是那个峰顶。
【全部代码】
/**
* // This is MountainArray's API interface.
* // You should not implement it, or speculate about its implementation
* interface MountainArray {
* public int get(int index) {}
* public int length() {}
* }
*/
class Solution {
public int findInMountainArray(int target, MountainArray mountainArr) {
if (mountainArr==null || mountainArr.length()==0)
return -1;
int l,top,leftIndex;
top = getTop(mountainArr);
l = 0;
leftIndex = getAsc(mountainArr,target,l,top);
if (leftIndex != -1)
return leftIndex;
else
return getDesc(mountainArr,target,top,mountainArr.length()-1);
}
/**
* 求得山峰的峰顶元素
* */
public int getTop(MountainArray mountainArr) {
int left,right,mid,midVal,midAf;
left = 0;
right = mountainArr.length()-1;
while (left < right) {
mid = left + (right - left) / 2;
midVal = mountainArr.get(mid);
midAf = mountainArr.get(mid+1);
if (midAf > midVal)
left = mid + 1;
else
right = mid;
}
return left;
}
/**
* 左边升序求指定元素
* */
public int getAsc(MountainArray mountainArr, int target, int l ,int r) {
while (l < r) {
int mid = l + (r - l) / 2;
if (mountainArr.get(mid) < target)
l = mid + 1;
else r = mid;
}
if (mountainArr.get(l) == target)
return l;
else return -1;
}
/**
* 右边降序求指定元素
* */
public int getDesc(MountainArray mountainArr, int target, int l ,int r) {
while (l < r) {
int mid = l + (r - l) / 2;
if (mountainArr.get(mid) > target)
l = mid + 1;
else r = mid;
}
if (mountainArr.get(l) == target)
return l;
else return -1;
}
}
// 用于本地测试,提交的时候可以不写以下代码
class MountainArray {
ArrayList<Integer> table;
public MountainArray() {
table = new ArrayList();
}
public int get(int index) {
return table.get(index);
}
public void add(int val) {
table.add(val);
}
public void addAll(int[] a) {
for (int i=0; i<a.length; i++) {
table.add(a[i]);
}
}
public int length() {
return table.size();
}
}
【Java 面试那点事】
这里致力于分享 Java 面试路上的各种知识,无论是技术还是经验,你需要的这里都有!
这里可以让你【快速了解 Java 相关知识】,并且【短时间在面试方面有跨越式提升】
面试路上,你不孤单!