我们偶尔会遇到这样的二分查找问题,它的元素有重复,我们需要找到这个元素下标最小和最大的位置。或者它不是一种精确地查询,比如问一个排序数组中,大于等于6的最小数值是多少。
面对这样的问题呢,我们的核心思路仍然是用二分法,只不过我们查找到符合条件的元素后不会立刻停止,而是会继续查找,缩短范围。
它的核心还是判断。
举个例子,假设在[L, R]区间内进行二分查找,成立条件为Condition(X) == True。因此我们可以套用这样的模板
int l = L;
int r = R;
int res = -1;
while (l <= r)
{
int m = (l + r) / 2;
if (condition(M))
res = m;
缩短范围
else
缩短范围
}
if (res != -1)
找到范围内符合条件的结果
else
范围内无符合条件的结果
注意这个res变量,这就是我喜欢这种写法的地方。它不用靠循环结束后的l或者r来确定最终停在哪个位置。它始终保存着最后一次符合条件的结果的值。仔细思考思考,非常直观。
然后就是condition和缩短范围的写法。这里就要区分要找最小还是最大的概念。它决定了区间缩放的方向。
假如以上边的例子为例,我要找到大于等于6的最小值
那就应该这么写
if (m >= 6)
res = m;
r = m - 1;
else
l = m + 1;
如果要找到严格等于6的最左值或严格等于6的最右值,核心就是在找到元素时不要停止查找,而是缩短范围即可
最右
if (m == 6)
res = m;
l = m + 1;
else if (m < 6)
l = m + 1;
else
r = m - 1;
最右
if (m == 6)
res = m;
r = m - 1;
else if (m > 6)
r = m - 1;
else
l = m + 1;
以上就是简单的总结,其实分析一个问题能否用二分思想解决的时候,要注意的就是问题是否具有单调的性质,用白话来说,如果这个可以,那后边的都可以或者如果这个可以,那前边的都可以。这种二分很适合那些具有单调性质,验证答案很容易,但正面求解问题很难的情况