%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 二分查找
二分查找是一种在**有序数组**中查找**特定元素**的高效算法。
其基本思想是,通过**取待查找区间的中点**,**将待查找的区间分成两半**,**判断待查找的元素是否在区间的==左半部分==或==右半部分==**,然后**迭代地在相应的半区间内查找**,从而**逐步缩小查找范围**,直至**找到目标元素**或**确定目标元素不存在**(待查找区间收缩为空)。
## 二分思想的本质——二段性
从本质上来说,「二分」的思想是利用 "**==二段性==**" 来进行排除——**=="有一段区间满足某个性质,而另一段区间不满足某个性质"==,则可应用 "二分" 进行==折半查找==,每次==排除其中一段/一半区间==,从而==缩小搜索范围==**。
通常来说,"可**应用二分查找**的前提条件是**被查找/搜索的「==目标序列==」 ==有序==,具有==单调性==**",
但是, "**有序/单调性**" 并不是二分思想的**本质**,只是因为 **==有序序列==提供了这样的==二段性==**,
所以**能够在有序序列上运用二分搜索来查找目标值**。
<br>
## 有序序列上应用二分查找
在一个序列上**查找==序列中的某一目标值==** 时,**要求「目标序列」具有单调性**。
**只有这样,才能保证每次比较后,==一定能排除一半元素,从而缩小搜索范围**==。
> [!caution] 要求的是 "**被查找==目标==所构成的序列**" 必须有序,而不是**原序列**。
在部分场景下,**尽管==原序列有序==,但基于原序列各项元素所得的==新元素构成的序列==不一定有序,因此不能在这一==新序列==上进行二分查找**。
例如:
❌ 查找**有序数组中首个"值大于等于其下标"的元素**,**==不能==** 使用二分查找:
对每个 `i`,被查找的目标实际上是 `nums[i]>=i`,即 `nums[i]-i>=0` ,而 **`nums[i]-i` 并不保证有序**,因此**不能使用二分查找**。
例如对序数组 `{0, 1, 2, 2, 2, 5}` ,正确答案是 0,而 `nums[i]-i` 构成的新数组为 `{0, 0, 0, -1, -2, 0}`,不能应用二分查找。
✔️ 查找**升序数组中首个满足 `"nums[i]>=n-i"` 的元素**,可以使用二分查找:
对每个 `i`,**被查找的目标**实际上是 `nums[i]+i>=n`,`nums[i]` 和 `i` 都是升序的,
因此 `nums[i] + i` 也是有序的,**可以对 `nums[i] + i` 进行二分查找**。
✔️ 查找**降序数组中首个满足 `"nums[i]>= i+1"` 的元素**,可以使用二分查找:
对每个 `i`,**被查找的目标**实际上是 `nums[i]-i>=1`,**`nums[i]` 是降序**而 `i` 是升序的,
因此 `nums[i] - i` 仍然是降序的,**可以对 `nums[i] - i` 进行二分查找**。
<br><br>
# 二分查找的使用 ⭐
给定目标值 `k`,可在 **有序数组** 上查找(以**升序数组**为例):
- **恰等于目标值 `k` 的元素下标**
- **左边界**:
- **`>=k`** 的 **==最小==** 元素下标
- **`>k`** 的 **==最小==** 元素下标:
- **右边界**:
- **`<=k`** 的 **==最大==** 元素下标:
- `<k` 的 **==最大==** 元素下标`
## 找目标值
###### 检查 `==k` 是否存在
```cpp
bool exist = binary_search(arr.begin(), arr.end(), k);
return exit;
```
###### 找值 `==k` 的**最小**下标(相当于找**左边界**)
注意:必须检查**位置是否==有效==(`!=arr.end()`)** & 检查 **`lower_bound(k)`是否==恰等于 k==**。
```cpp
int idx = lower_bound(arr.begin(), arr.end(), k) - arr.begin();
if (idx != n && arr[idx] == k) { // 检查下标有效 & arr[idx]恰为k
return idx; // arr[idx] == k.
}
return -1; // 不存在k
```
###### 找值 `==k` 的**最大**下标(相当于找**右边界**)
注意:必须检查**位置是否==有效==(`!=arr.begin()`)** & 检查 **`upper_bound(k) - 1`是否==恰等于 k==**。
> [!NOTE] `upper_bound()` 找大于`k` 的首项,相当于得到的是 **==右开边界==**:`[, r)`
```cpp
int idx = upper_bound(arr.begin(), arr.end(), k) - arr.begin();
if (idx != 0 && arr[idx - 1] == k) {
return idx - 1; // arr[idx - 1] == k.
}
return -1; // 不存在k
```
## 查找左边界
###### 找值 `>=k` 的最小下标
```cpp
int idx = lower_bound(arr.begin(), arr.end(), k) - arr.begin();
if (idx != n) return idx;
reutrn -1; // 不存在, 元素全<k.
```
###### 找值 `>k` 的最小下标
```cpp
int idx = upper_bound(arr.begin(), arr.end(), k) - arr.begin();
if (idx != n) return idx;
return -1; // 不存在, 元素全<=k.
```
## 查找右边界
对"**右边界**'" 的求解,均可**转换为对 "==左边界==" 的求解**。
- 「**`< k` 的最大元素下标**」 等价于「**`>=k` 的最小元素下标 ==- 1==**」
- 「**`<=k` 的最大元素下标** 」等价于「**`> k` 的最小元素下标 ==- 1==** 」
###### 找值 `<k` 的最大下标
```cpp
int idx = lower_bound(arr.begin(), arr.end(), k) - arr.begin();
if (idx != 0) return idx - 1; //
reutrn -1; // 不存在, 元素全>=k.
```
###### 找值 `<=k` 的最大下标
```cpp
int idx = upper_bound(arr.begin(), arr.end(), k) - arr.begin();
if (idx != 0) return idx - 1;
return -1; // 不存在, 元素全 > k.
```
## 查找范围
> [!example] 示例:[[99-题解/leetcode/lc-34 在排序数组中查找元素的第一个和最后一个位置|lc-34 在排序数组中查找元素的第一个和最后一个位置]]
###### 找值 `==k` 的区间范围 `[l, r)`
```cpp
int l = lower_bound(arr.beign(), arr.end(), k) - arr.begin(); // nums[l]>=k 的最小l.
int r = upper_bound(arr.begin(), arr.end(), k) - arr.begin(); // nums[r] >k 的最小r.
int length = r - l; // 区间[l, r)长度为 r - l.
```
- 若 `nums` 中所有元素均 `< k`,则 `l==r==n`,此时区间为空;
- 若 `nums` 中所有元素均 `> k`,则 `l==r==0`,此时区间也为空。
###### 找值 `lo <= val <= hi` 的区间范围 `[l, r)`
```cpp
int l = lower_bound(arr.beign(), arr.end(), lo) - arr.begin(); // nums[l]>=lo 的最小l.
int r = upper_bound(arr.begin(), arr.end(), hi) - arr.begin(); // nums[r] >hi 的最小r.
int length = r - l; // 区间[l, r)长度为 r - l.
```
## 查找最接近的值
查找数组中 **==最接近== `k` 的值**的下标位置:
1. 找 `k` **右侧(`>k`)与其最接近的值**的下标位置 `hi`,
则 `k` **左侧及本身(`<=k`)与其最接近的值**的下标位置 `lo = hi - 1`。
2. **比较 `arr[hi]` 与 `arr[hi-1]` 谁==更接近==** `k`。
```cpp
int min_diff = INT_MAX;
int hi = upper(arr.begin(), arr.end(), k) - arr.begin();
if (hi != n) {
min_diff = arr[hi] - k;
}
if (hi != 0) {
min_diff = min(min_diff, k - arr[hi-1]);
}
// 也可以是hi = lower(arr.begin(), arr.end(), k) - arr.begin();
```
## 统一使用 `lower_bound`
> [!NOTE] 在整数数组中,四种情况全都可以**统一为==基于 `lower_bound()` 求解==**:
>
> - 「**`> k` 的最小元素下标**」 等价于「**`>=(k+1)` 的最小元素下标**」
> - 「**`<=k` 的最大元素下标** 」等价于「**`>=(k+1)` 的最小元素下标 ==- 1==** 」
> - 「**`< k` 的最大元素下标**」 等价于「**`>=k` 的最小元素下标 ==- 1==**」
在 **==整数==** 数组中,**查找左右边界(`≥, >, <, ≤` 目标值`k`的四种情况)都可以直接基于 `lower_bound(k)` 解决**:
* `≥ k` 的最小元素下标(**左**边界):`lower_bound(k)`
* `> k` 的最小元素下标(**左**边界):`lower_bound(k+1)` 或 `upper_bound(k)`;
* `< k` 的最大元素下标(**右**边界):`lower_bound(k)-1` 即**大于等于`k`的首项元素的左侧一项**。
* 若 `lower_bound(k) != arr.begin()`,则存在。
* `≤ k` 的最大元素下标(**右**边界):`lower_bound(k+1)-1` 或 `upper_bound(k)-1`,即**大于`k`的首项元素的左侧一项**。
* 若 `lower_bound(k+1)` 或 `upper_bound(k)` 满足 `!=arr.begin()`,则存在。
- `== k` 的最小元素下标(**左**边界):`lower_bound(k)` & 检查**是否==有效==(`!=arr.end()`)** & 检查 **`lower_bound(k)`是否==恰为 k==**
- `== k` 的最大元素下标(**右**边界):`lower_bound(k+1)` & 检查**是否==有效== `!=arr.begin()`** & 检查 `lower_bound(k+1)-1` **是否==恰为`k`==**
> [!caution] 注意找「右边界」时的越界判断
> 通过`lower_bound(k) - 1` 以及 `upper_bound(k) - 1`找**右边界时**:
> - 若 `lower_bound(k)` 或 `upper_bound(k)` 返回 `arr.end()` ,**==减一的位置==也是==有效==的**。
> - 仅当 `lower_bound(k)` 或 `upper_bound(k)` 返回 `arr.begin()` 时,意味着**未找到/不存在**,此时**再`-1` 会==越界==**。
<br><br><br>
# 二分查找模版
![[00-数据结构与算法笔记/基本算法/二分查找模版.canvas|二分查找模版]]
## 二分查找的实现
根据以下对「**==循环不变量==** 」定义的不同,二分查找有多种不同的具体实现方式,各种方式的细节处理不同。
- 「区间」的含义:
- "**剩余未检查的区间**" ,当 `mid` 恰为查找目标时,**区间要==跳过 `mid`,不包含==**; ❇️
- "**答案可能存在的区间**",当 `mid` 恰为查找目标时,**区间要==包含 `mid`==**;
- 「区间」的形式:
- **左闭右开区间** 或 **闭区间**
> [!tip] 推荐对区间的定义采用 "**==剩余未检查的区间==**",这样无论是**闭区间还是开区间**,**`mid` 都无需向上取整**。
>
> MinGW 中对 `lower_bound()` 的实现逻辑里,循环条件即是 "**==剩余未检查区间==**"。不同在于,它的 **`mid` 采用了上取整**。
>
> ![[_attachment/00-数据结构与算法笔记/基本算法/二分查找.assets/IMG-二分查找-DEEC93A9250C837FCCA4BB6D737AB77E.png|950]]
>
> [!caution] 第二种区间含义(区间内包含可能的目标)存在瑕疵:
>
> - **当 `mid` 满足条件而需要收缩左边界**时(例如找 `>=mid` 的最后一项元素),需要取 `l == mid`,若元素数量≤2,则这在 `mid` 向下取整时会导致死循环,因此对应的 **`mid` 必须采用==上取整==** 。
>
> >
> - 当采用左闭右闭区间写法时循环条件应为`while (l < r)`,最后收缩到`l==r` 而跳出循环后需要**再额外进行一次 `check()` 判断最终结果,这个`check()`调用本身可能很耗时**.
> [!caution] 循环不变量定义采用 "答案可能存在的区间" 时,需要特别注意边界,避免死循环
>
> 为避免死循环,思考清楚下列两种特殊情况即可:
>
> - **`l` 和 `r` ==相邻==** 时,是否可能陷入死循环?
> - 如果可能,**则 `mid` 下取整还是上取整能避免死循环?**
> - **`l` 和 `r` ==重合==** 时,是否可能陷入死循环?
> - 如果可能,则 **`l==r`时需要==跳出循环==单独判断**
<br><br>
# 「左闭右开」区间表示"剩余待搜索区间"
循环不变量: `[l, r)` 表示**剩余==待搜索==区间**:
- **初始化**: `l = 0, r = n;`
- **循环条件**:`l < r` ,表示**还存在剩余待搜索空间**。
- **循环结束**时:`l == r`
- 找 **==左==边界**:升序数组中**找 `>=k` 或 `>k` 的 "==最小下标==元素"**
- 每当 `mid` 为目标值时 **`r` 都会收缩至 `r = mid`**; (取 `r`)
- 因此最终**若 ==`r != n`== 则 ==`r`== 即为所查找的目标**。
- 若目标不存在,则 `l` 一直会收缩至 `l==r==n`(越界)
- 找 **==右==边界**:升序数组中**找 `<= k` 或 `<k` 的 "==最大下标==元素"**
- 每当 `mid` 为目标值时 **`l` 都会收缩至 `l = mid+1`**,(取 `l - 1`)
- 因此最终**若 ==`l != 0`== 则 ==`l - 1`== 即为所查找的目标**。
- 若目标不存在,则 `r` 一直会收缩至 `l==r==0` ,此时 `l-1` 越界 ^1dce49
![[Excalidraw-Solution/二分查找_0.excalidraw.svg|550]]
%%[[Excalidraw-Solution/二分查找_0.excalidraw.md|🖋 Edit in Excalidraw]]%%
> [!caution] 如果 `n` 的范围是 `n <= INT_MAX`,则必须用 `long long` 来处理, <br> 否则无法表示右开边界 `r == INT_MAX + 1`
##### 找目标值
```cpp
// 查找值恰为k的元素的下标.
int BinarySearch(vector<int>& arr, int k) {
int l = 0, r = arr.size();
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] == k) {
return mid;
} else if (arr[mid] < k) {
l = mid + 1;
} else {
r = mid;
}
}
return -1;
// 如果要求未找到时返回可插入位置, 则直接return l或r.(会是-1或者n)
}
```
##### 找左边界
```cpp
// 找值大于等于k的最小下标
int lower_bound(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n;
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] >= k) {
r = mid;
} else {
l = mid + 1;
}
}
if (r != n) return r;
return -1;
// 若不存在>=k的元素, 则l收缩到l==r==n
// 若要求未找到时返回可插入位置(即n), 则直接return l或r.
}
// 找值大于k的最小下标
int upper_bound(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n;
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] > k) {
r = mid;
} else {
l = mid + 1;
}
}
if (r != n) return r;
return -1;
// 如果要求未找到时返回可插入位置(即n), 则直接return l或r.
}
```
##### 找右边界
```cpp
// 找值小于等于k的最大下标
int r_le_bound(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n;
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] <= k) {
l = mid + 1;
} else {
r = mid;
}
}
if (l != 0) return l - 1;
return -1;
// 如果要求未找到时返回可插入位置(即-1), 则直接return l - 1;
}
// 找值小于k的最大下标
int r_lt_bound(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n;
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] < k) {
l = mid + 1;
} else {
r = mid;
}
}
if (l != 0) return l - 1;
return -1;
// 如果要求未找到时返回可插入位置(即-1), 则直接return l - 1;
}
```
<br><br>
# 「左闭右闭」区间表示 "剩余待搜索区间"
循环不变量: `[l, r]` 表示**剩余==待搜索==区间**:
- **初始化**: `l = 0, r = n - 1;`
- **循环条件**:==`l <= r`==,表示**还存在剩余待搜索空间**
- **循环结束**时:`l == r + 1`
- 找 **==左==边界**:升序数组中**找 `>=k` 或 `>k` 的 "==最小下标==元素"**
- 每当 `mid` 为目标值时 **`r` 都会收缩至 `r = mid - 1`**,因此最终**若 ==`l != n`== 则 ==`l`==(也即`r + 1`) 即为所查找的目标**。
- 若目标不存在,则最终 `l` 会收缩至 `l==r+1==n`(**越界**)
- 找 **==右==边界**:升序数组中**找 `<= k` 或 `<k` 的 "==最大下标==元素"**
- 每当 `mid` 为目标值时 **`l` 都会收缩至 `l = mid+1`**,因此最终**若 ==`l != 0`== 则 ==`l - 1`== 即为所查找的目标**。
- 若目标不存在,则最终 `r` 会收缩至 `r==l-1==-1` (**越界**)
![[Excalidraw-Solution/二分查找_1.excalidraw.svg|550]]
%%[[Excalidraw-Solution/二分查找_1.excalidraw.md|🖋 Edit in Excalidraw]]%%
> [!caution] 如果 `n` 的范围是 `n <= INT_MAX`,则必须用 `long long` 来表示,或者在 `l == INT_MAX` 并且即将越界时跳出。
>
##### 找目标值
```cpp
int BinarySearch(std::vector<int>& arr, int k) {
int l = 0, r = arr.size() - 1;
while (l <= r) {
int mid = l + (r - l)/2;
if (arr[mid] < k) {
l = mid + 1;
} else if (arr[mid] > k) {
r = mid - 1;
} else {
return mid;
}
}
return -1;
}
```
##### 找左边界
```cpp
// 找值大于等于k的最小下标
int lower_bound(std::vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (arr[mid] >= k) {
r = mid - 1;
} else {
l = mid + 1;
}
}
if (l == n) return -1;
return l;
// 若不存在>=k的元素, 则l收缩到l==r+1==n.
// 若要求未找到时返回可插入位置(即n), 则直接return l或r+1.
}
// 找值大于k的最小下标
int upper_bound(std::vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (arr[mid] > k) {
r = mid - 1;
} else {
l = mid + 1;
}
}
if (l == n) return -1; // 若不存在>=k的元素, 则l收缩到l==r+1==n.
return l;
}
```
##### 找右边界
```cpp
// 找值小于等于k的最大下标
int r_le_bound(std::vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (arr[mid] <= k) {
l = mid + 1; // mid满足, 剩余区间[mid+1, r]待搜索. 明确l-1满足条件
} else {
r = mid - 1;
}
}
// 最后l==r+1, 若存在满足条件的目标, r即为目标下标; 否则, r收缩至r==l-1==-1.
return r;
}
// 找值小于k的最大下标
int r_lt_bound(std::vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (arr[mid] < k) {
l = mid + 1; // mid满足, 剩余区间[mid+1, r]待搜索. 明确l-1满足条件
} else {
r = mid - 1;
}
}
// 最后l==r+1, 若存在满足条件的目标, r即为目标下标; 否则, r收缩至r==l-1==-1.
return r;
}
```
<br><br>
# 「左闭右闭」区间表示 "答案可能存在的区间"
循环不变量: `[l, r]` 表示 **==包含可能答案的区间==**:
- **初始化**: `l = 0, r = n - 1;`
- **循环条件**:==`l < r`==,
- 若 `l != r`,表示**还需要进一步收缩可能存在答案的空间**
- 当 `l == r` 时,表示**只剩下这唯一的位置==可能包含答案==**,需**跳出循环,单独判断**。
- **循环结束**时:`l == r`
- 在循环体外,需要 **==再进行一次 `check`==**,判断最终 **`l==r` 的位置是否即为目标值**。
> [!NOTE]
>
> 在前两种**开区间/闭区间**写法中,区间本身表示的是 "**剩余待搜索的空间**",因此当空间收缩到为空时,表示整个区间都已搜寻过,通过**判断边界位置是否越界即知答案是否存在**。
>
> 而在当前这种写法中,区间 `[l, r]` 表示的是 **==包含可能答案的区间==**,因此当最后 `l==r` 时,**==可能包含答案的空间收缩为 1==**,此时**需跳出循环(避免死循环)**,**==单独对这一个位置==检查是否包含目标值**。
- 找 **==左==边界**:升序数组中**找 `>=k` 或 `>k` 的 "==最小下标==元素"**
- `mid` 下取整
- **区间 `[l, r]` 被划分成 ==`[l, mid]`== 和 ==`[mid + 1, r]`==** ,每次 **==排除掉其中一半区间==**。
- 若 `mid` 满足条件,则排除右半区间 `[mid+1, r]`,收缩 `r = mid`;
- 若 `mid` 不满足,则排除左半侧区间 `[l, mid]`,收缩 `l = mid+1`;
- 当 `l==r`时跳出循环,**检查 `arr[l]` 是否为查找目标**;
- 注意检查**数组非空**:`n > 0 && check(arr[l]);`
- 找 **==右==边界**:升序数组中**找 `<= k` 或 `<k` 的 "==最大下标==元素"**
- **`mid` ==上取整==(`mid = l + (r - l + 1) / 2`)**
- **区间 `[l, r]` 被划分成 ==`[l, mid - 1]`== 和 ==`[mid, r]`==** ,每次 **==排除掉其中一半区间==**。
- 若 `mid` 满足条件,则排除左半区间 `[l, mid - 1]`,收缩 `l = mid`;
- 若 `mid` 不满足,则排除右半侧区间 `[mid, r]`,收缩 `r = mid - 1`;
- 当 `l==r`时跳出循环,**检查 `arr[l]` 是否为查找目标;
- 注意检查**数组非空**:`n > 0 && check(arr[l]);`
![[Excalidraw-Solution/二分查找_2.excalidraw.svg|550]]
%%[[Excalidraw-Solution/二分查找_2.excalidraw.md|🖋 Edit in Excalidraw]]%%
##### 找目标值
```cpp
int binary_search(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] == k) {
return mid;
} else if (arr[mid] < k) {
l = mid + 1;
} else {
r = mid;
}
}
return (n > 0 && arr[l] == k) ? l : -1;
}
```
##### 找左边界
```cpp
int lower_bound(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] >= k) {
r = mid;
} else {
l = mid + 1;
}
}
return (n > 0 && arr[l] >= k) ? l : -1;
// 如果要求未找到时返回可插入位置(即n), 则需要return l + 1.
}
int upper_bound(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (arr[mid] > k) {
r = mid;
} else {
l = mid + 1;
}
}
return (n > 0 && arr[l] > k) ? l : -1;
// 如果要求未找到时返回可插入位置(即n), 则需要return l + 1.
}
```
##### 找右边界
```cpp
int r_le_bound(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l < r) {
int mid = l + (r - l + 1) / 2;
if (arr[mid] <= k) {
l = mid;
} else {
r = mid - 1;
}
}
return (n > 0 && arr[l] <= k) ? l : -1;
// 如果要求未找到时返回可插入位置(即-1), 则需要return l - 1.
}
int r_lt_bound(vector<int>& arr, int k) {
int n = arr.size();
int l = 0, r = n - 1;
while (l < r) {
int mid = l + (r - l + 1) / 2;
if (arr[mid] < k) {
l = mid;
} else {
r = mid - 1;
}
}
return (n > 0 && arr[l] < k) ? l : -1;
// 如果要求未找到时返回可插入位置(即-1), 则需要return l - 1.
}
```
##### 例题
[[99-题解/leetcode/lc-1533 找到最大整数的索引|lc-1533 找到最大整数的索引]]
<br><br>
# C++标准库中提供的二分查找函数
C++标准库提供了**四个二分查找**相关的函数:
- `upper_bound(first, last, val);`
在**升序序列** 区间 `[first, last)` 中二分查找并返回首个**元素值==大于== `val` 的位置**(左边界)
不存在时,返回 `last`;
- `lower_bound(first, last, val);`
在**升序序列** 区间 `[first, last)` 中二分查找并返回首个**元素值==大于等于== `val` 的位置**(左边界)
不存在时,返回 `last`;
- `binary_search(first, last, val);`
在**序列**区间 `[first, last)` 中二分查找,检查**是否存在目标值 `val`**,返回 `true/false`;
- `equal_range(first, last, val);`
在**序列**区间 `[first, last)` 中二分查找,得到一个**元素值全等于目标值 `val` 的区间范围**,
- 返回 `pair<ForwardIt, ForwardIt>` 类型的结果 `{begin}`
`{lower_bound(first, last, val), upper_bound(first, last, val)}`
> [!NOTE] `equal_range()` 使用示例
>
> ```cpp
> auto p = equal_range(nums.begin(), nums.end(), target);
> // 如果没有找到目标值,则返回{-1, -1}
> if (p.first == p.second) return {-1, -1};
> ```
---
搭配 `std::greater<T>()`,可用于在 **==降序==** 序列中进行二分查找:
- `lower_bound(first, last, val, std::greater<T>());`
在**降序序列**区间 `[first, last)` 中二分查找并返回首个**元素值==小于等于== `val` 的位置**(左边界)
- `upper_bound(first, last, val, std::greater<T>());`
在**降序序列**区间 `[first, last)` 中二分查找并返回首个**元素值==小于== `val` 的位置**(左边界)
<br>
### 函数原型
函数原型示例如下,默认使用 `std::less<T>()`,在 **==升序序列==** 中查找。
```cpp
template <class ForwardIt, class T>
bool binary_search(ForwardIt first, ForwardIt last, const T& value);
template <class ForwardIt, class T, class Compare>
ForwardIt upper_bound (ForwardIt first, ForwardIt last, const T& val, Compare comp);
template <class ForwardIt, class T, class Compare>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value, Compare comp);
template <class ForwardIt, class T, class Compare>
pair<ForwardIt,ForwardIt> equal_range(ForwardIt first, ForwardIt last, const T& value);
```
> ![[_attachment/00-数据结构与算法笔记/基本算法/二分查找.assets/IMG-二分查找-CA325BBBF83BF0399B678BB1AEF1DFD8.png|598]]
> ![[_attachment/00-数据结构与算法笔记/基本算法/二分查找.assets/IMG-二分查找-5C121B7F21FC7568280DB6458AC7817F.png|600]]
<br><br>
### 自定义 Compare 函数
- `lower_bound` 返回 `[first, last)` 区间中 ==`comp(*iter, value) == false`== 的 **首个位置**;
- 默认为`std::less`,即 `*iter < value == false` 的首个位置
- `upper_bound` 返回 `[first, last)` 区间中 ==`comp(value, *iter) == true`== 的**首个位置**;
- 默认为 `std::less`,即 `value < *iter == true` 的首个位置
%%
# 三分法
**「三分」就是使用两个端点将区间分成三份,然后通过每次否决三分之一的区间来逼近目标值。**
对于区间 `[l, r]`,划分成三等分 `[l, m1]`,`[m1, m2]`,`[m2, r]`:
- `m1 = l + (r - l) / 3;`
- `m2 = r - (r - l) / 3;`
%%
<br><br>
# 参考资料
[六款二分查找模板,哪款才是你的菜?](https://leetcode.cn/circle/discuss/ObmjbJ/)
[二分查找 红蓝染色法-灵茶山艾府\_哔哩哔哩\_bilibili](https://www.bilibili.com/video/BV1AP41137w7/?spm_id_from=333.788.recommend_more_video.4&vd_source=94d94bad947a5b38e63f1f55450429ab)
# Footnotes