mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-22 11:26:07 +00:00
9135647d93
Add dedicated chapter exercise sections and generated exercise pages for the English, Japanese, and Russian editions. Add navigation for all published languages while keeping Taiwan Traditional Chinese exercise pages generated at build time.
98 lines
4.5 KiB
Markdown
98 lines
4.5 KiB
Markdown
<!-- 此文件由 utils/exercises/publish_exercises.py 根据 exercises.yaml 自动生成,请勿直接修改。 -->
|
|
|
|
# 练习
|
|
|
|
## 知识巩固
|
|
|
|
### 二分查找怎样缩小搜索区间
|
|
|
|
在有序数组 `[2, 5, 8, 12, 16, 23, 38]` 中查找 16。
|
|
使用双闭区间 `[i, j]`,中点为
|
|
$m=i+(j-i)/2$(向下取整)。
|
|
|
|
请写出每轮的 `(i, j, m)`、中点元素,以及下一步如何缩小区间,
|
|
直到找到目标。
|
|
|
|
??? success "参考答案"
|
|
|
|
每轮查找过程如下:
|
|
|
|
| 轮次 | `(i, j, m)` | 中点元素 | 下一步 |
|
|
| --- | --- | --- | --- |
|
|
| 1 | `(0, 6, 3)` | 12 | `12 < 16`,令 `i = 4` |
|
|
| 2 | `(4, 6, 5)` | 23 | `23 > 16`,令 `j = 4` |
|
|
| 3 | `(4, 4, 4)` | 16 | 找到目标,返回索引 4 |
|
|
|
|
数组有序,所以中点值小于目标时可以排除中点及其左侧;
|
|
中点值大于目标时可以排除中点及其右侧。
|
|
|
|
### 重复元素的左右边界
|
|
|
|
在数组 `[1, 2, 2, 2, 4, 6]` 中查找数字 2。
|
|
一名同学用二分查找在索引 2 找到目标后立即返回,并说:
|
|
“索引 2 就是数字 2 的左边界。”
|
|
|
|
<!-- numbered-subquestions -->
|
|
|
|
1. 这名同学的说法是否正确?数字 2 的左、右边界分别是多少?请说明理由。
|
|
2. 查找左边界时,若中点元素等于目标,接下来应继续搜索哪一侧?
|
|
3. 查找右边界时又应继续搜索哪一侧?只需说明方向,无须写出完整的查找过程。
|
|
|
|
??? success "参考答案"
|
|
|
|
1. 这名同学的说法不正确。找到一个 2 后立即返回,只能保证找到某一个 2,不能保证找到最左或最右的 2。
|
|
本题的左边界为索引 1,右边界为索引 3。
|
|
|
|
2. 查找左边界时,即使中点元素等于 2,也要继续搜索左侧,
|
|
例如在双闭区间写法中令 `j = m - 1`。
|
|
|
|
3. 查找右边界时,中点元素等于 2 后应继续搜索右侧,
|
|
例如令 `i = m + 1`。
|
|
|
|
### 不同数据该选哪种搜索方法
|
|
|
|
请在“顺序查找、二分查找、哈希表”中,为下面三个场景选择合适的方法,并说明理由:
|
|
|
|
<!-- numbered-subquestions -->
|
|
|
|
1. 在 $10^7$ 个已经有序且不再变动的整数中反复查找,不额外建立其他数据结构;
|
|
2. 在频繁插入、删除的数据集中反复判断某个键是否存在,不要求保持有序,也不进行范围查找;
|
|
3. 在无序数组中只查找一次某个值。
|
|
|
|
??? success "参考答案"
|
|
|
|
1. 二分查找:数据有序且静态,$O(\log n)$ 且无须额外空间。
|
|
2. 哈希表:当哈希函数能把键较均匀地分散到各桶时,插入、删除和按键查找的平均时间复杂度都是 $O(1)$。
|
|
3. 直接从头到尾遍历:只查找一次时,排序或建立哈希表都要先处理整个数组,
|
|
并不会减少这一次任务的总工作量。
|
|
|
|
选择取决于数据是否有序、是否允许建立额外结构、查询次数以及需要执行哪些操作。
|
|
|
|
## 编程练习
|
|
|
|
### 有序数组的二分查找
|
|
|
|
给定一个按严格递增顺序排列的整数数组 `nums` 和目标值 `target`。请使用二分查找找到 `target`:若存在,返回它的数组索引;若不存在,返回 -1。
|
|
|
|
??? tip "解题提示"
|
|
|
|
1. 初始区间是 left = 0、right = n - 1,非空条件是 left <= right
|
|
2. 用 mid = left + (right - left) // 2 计算中点
|
|
3. 若 `nums[mid]` 小于 `target`,就把左边界移到 `mid + 1`;若 `nums[mid]` 大于 `target`,就把右边界移到 `mid - 1`;相等时立即返回
|
|
|
|
[LeetCode](https://leetcode.cn/problems/binary-search/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" } [题目解析](https://leetcode.cn/problems/binary-search/solutions/1692151/by-jyd-i7xr/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|
|
|
|
### 有序数组的插入位置
|
|
|
|
给定一个按严格递增顺序排列的整数数组 `nums` 和目标值 `target`。
|
|
|
|
若 `target` 已在数组中,返回它的索引;否则,返回把 `target` 插入数组后仍能保持严格递增顺序的位置。答案可能是 0,也可能等于数组长度。请使用二分查找。
|
|
|
|
??? tip "解题提示"
|
|
|
|
1. 答案可能是 0,也可能是数组长度 n
|
|
2. 使用双闭区间时,若 `nums[mid]` 大于等于 `target`,就令 `right = mid - 1`,继续检查更靠左的位置;否则令 `left = mid + 1`
|
|
3. 循环结束时 left 就是插入位置
|
|
|
|
[LeetCode](https://leetcode.cn/problems/search-insert-position/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }
|