继leetcode(1) 和leetcode(2)后 有第三种二分方法
也是最推荐的一种 包括后面很多的复杂的二分问题用的是第三种二分算法、
第三种方法 严格运用了 二分算法的精髓 也就是二段性

我们把数组分成两部分

一部分是小于target的 称为左半区间或左区间
一部分是大于或者大于等于target的称为右半区间或右区间
如果存在target 右半区间就是大于等于target的区间
如果不存咋target 右半区间就是大于target的区间
由于数组是有序的 所以target如果存在 只可能存在右半区间的第一个元素
这个元素的下标称为key 也就是图片中的红色箭头所指的位置
如果右半区间的第一个元素不是target 说明target不存在
所以我们的核心目标是找到key所对应的值
如果这个值等于target 那么target存在 返回下标key
如果这个值不等于target 那么target不存在 返回-1
mid求法以及原因和前两种方法相同 都是 mid=left+(right-left)/2
那么mid可能落在左区间和右区间就要分类讨论了
第一种情况 :mid落在左区间

这个时候mid落于左半区间
此时我们看图可知 key存在于mid和right之间
所以这个时候left就要更新
但是为什么更新是left=mid+1 而不是其他的呢?
但是前面我们知道了 key落在右半区间
所以mid指向的一定不可能是target
这个时候我们要缩小key存在的区间范围
但是我们只知道mid不可能是target
但是mid+1不确定
所以说mid+1 到right这个区间都可能是mid存在的地方
所以left=mid+1
第二种情况 :mid落在右区间

这个时候mid落于右半区间
此时我们看图可知 key存在于left和mid之间
所以这个时候right就要更新
但是我们知道key就在右区间
也就是mid有可能是key
所以这个时候取right=mid
因为如果取right=mid-1或者其他的
当mid=key的时候 我们就会错过我们要找的key的位置
那么while循环的条件是什么呢? 也就是啥时候停止while循环呢?
今天这个方法是当left=right的时候结束循环
那么为什么这个地方是和第一种方法的left>right结束循环条件不一样呢???
第一种方法的循环我是找值 我要把所有可能是target的值都要找一遍
我不确定target的位置 我要全部找一遍
但是今天的这种方法 我明确了 target存在一定在key这个位置
所以我们只要确定了这个key的位置
如果这个位置的值不等于target 那么target就不存在
如果等于 就直接返回target对应的下标key
我知道target存在一定在key的位置 只用找key的位置就可以了
那么我们还要证明几件事
你怎么知道left==right一定会发生
不会像第一种方法一样直接从left<right 到 left>right呢???
回顾一下 第一种方法 为什么会产生这种情况
第一就是我们这个mid取的偏左
比如说 一共有四个数 为 1 2 3 4 left指向1 right指向4
mid指向2 明显偏左
再加上第一种方法的right=--mid:导致的(详情见二分算法1)
但是这个地方我么right=mid
所以不会导致从left<right 直接跳到left>的情况
所以left==right一定存在
但是我们这个地方会不会陷入死循环呢
第一种方法不会陷入死循环 是因为第一种方法一次循环
哪怕mid等于left 或者right等于right的情况下 left和right都会相互靠近一步
(left=mid+1或right=mid-1)所以他们最后一定能满足left>right的情况
因为这个地方mid偏左 所以mid永远是不可能取到right
也就是mid<right
mid>=left
情况 1:
nums[mid] >= target → right = mid 新的区间为 [left, mid],
长度为 mid - left + 1。
由于 mid < right(原 right),新长度 mid - left + 1 < right - left + 1(原长度),区间长度严格减小。
情况 2:nums[mid] < target → 更新 left = mid + 1
此时新的区间为 [mid + 1, right],新长度为 new_len = right - (mid + 1) + 1 = right - mid。
由于 mid ≥ left(原 left),
因此: new_len = right - mid ≤ right - left(因为 mid ≥ left → right - mid ≤ right - left)。
而原长度 len = right - left + 1,显然 right - mid < right - left + 1(因为 right - mid ≤ right - left)。 即 区间长度也严格减小(例如:原长度 5 → 新长度 2)。
由于每次循环后,区间长度都会严格减小(从 len 变成更小的 new_len),
而初始区间长度是有限的(等于数组长度 n),
因此经过有限次循环后,区间长度一定会从 ≥2 减小到 1(即 left == right)。
此时循环条件 left < right 不再满足,循环终止。
整个过程中,区间长度不会 “停滞不变”,因此不可能陷入死循环。 反例:为什么 left = mid 可能导致死循环?
所以我们确定left==right作为循环停止条件 一定不会死循环
那么 我们怎么知道如果target存在
当left==right的时候 left和right一定指向target呢???
核心原因是 整个二分过程始终维持一个 “不变量”:
当前区间 [left, right] 一定包含 target(如果 target 存在)。
这个不变量从初始状态一直保持到循环结束,最终当区间收缩到只剩一个元素时,这个元素只能是 target。
循环终止的条件是 left == right,此时区间 [left, right] 中只有一个元素(left 和 right 指向同一个位置)。
由于整个过程中 “区间始终包含 target” 的不变量一直成立,这个唯一的元素只能是 target(否则就与 “区间包含 target” 矛盾)。
反证法:如果最终元素不是 target,会导致矛盾 假设 target 存在,
但 left == right 时指向的元素 x != target。
由于 x 是区间 [left, right] 的唯一元素,
且整个过程中区间始终包含 target,
则 x 必须等于 target(否则区间中没有 target,与 “target 存在” 矛盾)。
因此,假设不成立,left == right 时指向的元素必为 target。
所以我们确定当left==right的时候 left和right一定指向target
即可写出代码
class Solution {
public:
int search(vector<int>nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] >= target)
right = mid;
else left = ++mid;
}
if (nums[left] != target)
return -1;
return left;·
}
};
今天的这种方法比前两种方法的目的更明确 我知道我要找的就在key那里
通过二分找到key位置就可以
而不是像前两种方法一样 通过二分 把所有可能的值都找一遍
很好的运用了二段性 包括其他可以用二分解决的问题 核心都是二段性
三种方法中 最好理解的是第一种 但是适用性最广的是今天这种方法
2588

被折叠的 条评论
为什么被折叠?



