C++ 二分算法(3)

继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位置就可以

而不是像前两种方法一样 通过二分 把所有可能的值都找一遍 

很好的运用了二段性 包括其他可以用二分解决的问题 核心都是二段性

三种方法中 最好理解的是第一种 但是适用性最广的是今天这种方法

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值