二分是算法考察中的重要内容,通常用于在有序数组中进行查找,可以将时间复杂度从O(n)降至O(logn)。二分的思想十分简单,下面给出几种常见问题的模板写法。
1.二分查找
在递增数组中元素不重复,要求查找元素 x,查找成功返回 x 的下标,查找失败返回 -1。
public int binarySearch(int[] nums, int x) {
int left = 0, right = nums.length - 1;
while (left <= right) { // left == right 时也要判断一次
// left + right 可能超过 int 的最大值导致溢出
// Java 中有一种更好的写法是 mid = (left + right) >>> 1
// 不管 left + right 是否溢出都能得到正确的答案
int mid = (left + right) / 2;
if (nums[mid] == x) {
return mid;
} else if (nums[mid] > x) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
2.lowerBound
在递增数组中元素可能重复,要求找到第一个大于等于 x 的元素下标。
- 如果 nums[mid] >= x,说明第一个大于等于 x 的元素位置在 mid 或者 mid 左边
left | left+1 | … | mid-1 | mid | mid+1 | … | right-1 | right |
---|---|---|---|---|---|---|---|---|
a | b | c | x | x | x | x | d | e |
- 如果 nums[mid] < x,说明第一个大于等于 x 的元素位置在 mid 右边
left | left+1 | … | mid-1 | mid | mid+1 | … | right-1 | right |
---|---|---|---|---|---|---|---|---|
a | b | c | d | e | f | x | x | g |
于是可以得到如下的代码。一定要注意和上面 binarySearch 代码的区别!!!十分重要!!!
- right = nums.length 而不是 上面的 nums.length - 1
- 循环结束条件是 left < right 而不是 left <= right
public int lowerBound(int[] nums, int x) {
// right 一定要初始化为 length 而不是 length - 1,因为查找的元素可能越界
int left = 0, right = nums.length;
// left == right 说明找到了唯一位置,就是结果
// 所以与 binarySearch 不同,只需要 left < right 才循环
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] >= x) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
3.upperBound
在递增数组中元素可能重复,要求找到第一个大于 x 的元素下标。
- 如果 nums[mid] > x,说明第一个大于 x 的元素位置在 mid 或者 mid 左边
left | left+1 | … | mid-1 | mid | mid+1 | … | right-1 | right |
---|---|---|---|---|---|---|---|---|
a | x | x | b | c | d | e | f | g |
- 如果 nums[mid] <= x,说明第一个大于 x 的元素位置在 mid 右边
left | left+1 | … | mid-1 | mid | mid+1 | … | right-1 | right |
---|---|---|---|---|---|---|---|---|
a | b | c | x | x | x | x | d | e |
于是可以得到如下的代码。一定要注意和上面 binarySearch 代码的区别!!!十分重要!!!
- right = nums.length 而不是 上面的 nums.length - 1
- 循环结束条件是 left < right 而不是 left <= right
public int lowerBound(int[] nums, int x) {
// right 一定要初始化为 length 而不是 length - 1,因为查找的元素可能越界
int left = 0, right = nums.length;
// left == right 说明找到了唯一位置,就是结果
// 所以与 binarySearch 不同,只需要 left < right 才循环
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] > x) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
例题:力扣69.x的平方根[1]
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。
提示:
0 <= x <= 231 - 1
class Solution {
public int mySqrt(int x) {
int result = 0;
int left = 0, right = x;
while (left <= right) {
int mid = left + (right - left) / 2;
// 防止溢出需要转为 long
if ((long) mid * mid <= x) {
result = mid; // 只要小于x就能更新答案
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
}
Reference
[1]力扣69.x的平方根:https://leetcode-cn.com/problems/sqrtx