LeetCode HOT100 - 搜索旋转排序数组
我觉得挺有意思的一题,对于二分的理解应当是有帮助的
首先,二分的核心在于有序
我们通过中间点 mid 和 target 的大小关系可以知道它的一侧和 target 的关系
比如 mid 比 target 小,那么 mid 左侧的元素一定都比 target 小,进而我们只需要看右侧就可以了
现在回到这题
这题就是在于单调性的分成了两段
但是我们可以知道较大一段始终是在前面的
考虑现在 mid 比 target 大的情况,按照之前的说法来说就是 r = m ,缩小右边界
但因为我们是分开的,我们不能保证 target 会不会在可能的 mid 右侧的较小段
这时候,如果 target > a[r] , 那么就相当于较小段中一定不会有 target ,进而达到了"知道 mid 的一侧和 target 的关系"的目的,于是这种情况,右侧一定不可能,r = m
else 呢, target < a[r] 但是 mid 又 > target ,这实际上已经说明了 target 是在 mid 右侧的那个较小段中,这也说明了右侧是不单调的
但是,这同时说明了 target 不会在现在的 mid 中了
所以如果现在 target 在较小段中了,那么这就是有序的情形,按照一般二分的写法就可以,否则,我们就要让左边界到达较小段
如何判断是否在较小段中呢
显然,只要和当前 mid 判断就可以了,如果 mid 小于 a[r] 了,说明就是在较小段中了,正常更新,否则不在,那么就是 l = m + 1
我这里二分写的是左闭右开的形式,也就是 l 左侧一定不可行,r 及右侧一定可行
最终退出是 l == r
class Solution {
public:
int search(vector<int>& a, int target) {
int n = a.size();
int l = 0, r = n;
while (l < r) {
int m = (l + r) >> 1;
cout << l << ' ' << r << ' ' << m << '\n';
if (a[m] == target) {
return m;
}
if (a[m] < target) {
if (target < a[l]) {
l = m + 1;
} else if (a[m] < a[l]) {
r = m;
} else {
l = m + 1;
}
} else if (target > a[r - 1]) {
r = m;
} else if (a[m] > a[r - 1]) {
l = m + 1;
} else {
r = m;
}
}
cout << l << ' ' << r << '\n';
return l == n ? -1 : a[l] == target ? l : -1;
}
};
顺便回忆下二分的两种写法:
while (lo < hi) {
int u = (lo + hi + 1) / 2; // 上取整
if(x >= 1ll * u * (n + n - u + 1) / 2) {
lo = u; // 满足条件时保留中点
} else {
hi = u - 1; // 不满足时缩小右边界
}
}
采用的是上取整,因此得到的是最后一个满足条件的位置,此时左端点是开区间
while (lo < hi) {
int u = (lo + hi) >> 1; // 下取整
if(condition) {
hi = u; // 满足条件时缩小右边界
} else {
lo = u + 1; // 不满足时增大左边界
}
}
采用的是下取整,因此得到的是第一个满足条件的位置,此时右端点是开区间
看满足条件的最后一个位置的时候,可以看作是下面那个常见情况的反方向,这时候 lo , hi 相反,中位数要向 hi 靠近,因此需要是上取整

浙公网安备 33010602011771号