二分搜索
这里有一篇总结二分搜索非常好的Blog,链接贴在这,一定要看!!
一. 二分搜索基本应用场景
当一道题目出现了“有序N维数组中查找某个东西或最接近的值”时,大概率用二分搜索;或者输入的数字特别大,达到2^31 - 1左右的这种,也很有可能使用二分搜索。
其核心思想就是每次找一半,然后舍弃另一半,其中最恶心的莫过于有重复数字的二分搜索,太难顶了。例如:LeetCode 81的“Search in Rotated Sorted Array II”,到现在也不懂,真的难受,先记录着,等以后能搞懂了再回来看。
二. 二分搜索模板
基本模板
关于查找区间的问题,究竟是“左闭右开”还是“左右闭区间”,我在这里直接就用左右全闭区间的方法了。
while (l <= r) {
m = l + (r - l) / 2;
if g(m) == target :
return;
else if g(m) > target :
r = m - 1;
else if g(m) < target :
l = m + 1;
}
最终返回的时候,l一定是r右面的那个数,这是由while的循环条件决定的,二者最后只能差1。最后看情况是需要l还是r。
左边界 & 右边界
参考题目:下面例题 3.4 LeetCode 34. Find First and Last Position of Element in Sorted Array
关于左边界和右边界的问题只要简单说一下就懂了,举个例子,我们有一个数组nums:[1, 2, 6, 6, 10, 13, 17],我们想要找出数字6所在的位置,那么我们返回2还是返回3呢?虽然值是不同,但是对于实际应用的时候有很大不同。
比如说对于查找的时候,可能需要查找大于某个数的最小值,或者小于某个数的最大值,前者就是要找右边界,后者就是要找左边界(这里不敢保证写的绝对对啊,只是个人理解,有可能是错的)。
对于左右边界的处理其实不同点就在于等于的情况下,是更新left还是right。
左边界 -> 等于的时候更新右边界
while (l <= r) {
m = l + (r - l) / 2;
if g(m) >= target :
r = m - 1;
else if g(m) < target :
l = m + 1;
}
右边界 -> 等于的时候更新左边界
while (l <= r) {
m = l + (r - l) / 2;
if g(m) > target :
r = m - 1;
else if g(m) <= target :
l = m + 1;
}
返回l?返回r?
我们需要care的一点就是到底是返回l还是返回r呢?结论是:就题论题,九成以上返回l,但是不排除返回r的可能,例如上面的数组[1, 2, 6, 6, 10, 13, 17],找出比6小的最大值,自己写一下代码,很容易发现返回的是r,而不是l,因此如果不确定的话,最好的办法是找个例子尝试一下。
三. 二分搜索简单题目
其实二分搜索,难的是真滴难,简单的也有很多坑,慢慢积累经验吧,有时候边界条件搞不懂就特别烦。
3.1 LeetCode 69. Sqrt(x)
**注意:**需要注意的点就是输入的值可能会特别大,二分后的值,如果直接平方的话有可能会越界,所以可以考虑使用除法或者用long来实现。这里用除法。
class Solution {
public int mySqrt(int x) {
if (x <= 1) {
return x;
}
int l = 0, m = 0, r = x;
while (l <= r) {
m = l + (r - l) / 2;
if (m == x / m) {
return m;
} else if (m > x / m) {
r = m - 1;
} else if (m < x / m) {
l = m + 1;
}
}
return l - 1;
}
}
3.2 LeetCode 367. Valid Perfect Square
**注意:**需要注意的是这里不能用除法,和69题不一样,因为除法会损失精度,导致计算错误,所以这里用long类型来辅助计算。
class Solution {
public boolean isPerfectSquare(int num) {
int start = 1, end = num;
long target = 0;
while (start <= end) {
target = (start + end) / 2;
if (target * target > num) {
end = (int)target - 1;
} else if (target * target < num) {
start = (int)target + 1;
} else {
return true;
}
}
return false;
}
}
3.3 LeetCode 35. Search Insert Position
**注意:**这道是最传统的二分搜索,基础中的基础。
class Solution {
public int searchInsert(int[] nums, int target) {
int l = 0, m = 0, r = nums.length - 1;
while (l <= r) {
m = l + (r - l) / 2;
if (nums[m] == target) {
return m;
} else if (nums[m] < target) {
l = m + 1;
} else if (nums[m] > target) {
r = m - 1;
}
}
return l;
}
}
3.4 LeetCode 34. Find First and Last Position of Element in Sorted Array
**注意:**先找到一个pivot,再分别计算左右有多少。
class Solution {
public int[] searchRange(int[] nums, int target) {
int l = 0, r = nums.length - 1, m = 0, pivot = -1;
int[] res = new int[2];
while (l <= r) {
m = l + (r - l) / 2;
if (nums[m] < target) {
l = m + 1;
} else if (nums[m] >= target) {
r = m - 1;
}
}
if (l == nums.length || nums[l] != target) {
return new int[]{-1, -1};
}
// 这里l是第一个target的位置
res[0] = l;
r = nums.length - 1;
while (l <= r) {
m = l + (r - l) / 2;
if (nums[m] > target) {
r = m - 1;
} else {
l = m + 1;
}
}
res[1] = l - 1;
return res;
}
}
3.5 LeetCode 50. Pow(x, n)
**注意:**这道题要用二分法来减少运算,次数,而且负数除以2余数为-1,注意取反。
class Solution {
public double myPow(double x, int n) {
if (x == 0d) {
return 0;
}
if (n == 0) {
return 1;
} else if (n > 0) {
double ret = myPow(x, n / 2);
if (n % 2 == 1) {
return x * ret * ret;
} else {
return ret * ret;
}
} else {
double ret = myPow(x, n / 2);
if ((0 - n) % 2 == 1) {
return ret * ret / x;
} else {
return ret * ret;
}
}
}
}