hot100二分查找题解
35. 搜索插入位置
问题描述
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
1
2
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
1
2
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
1
2
输入: nums = [1,3,5,6], target = 7
输出: 4
提示:
- $1 <= nums.length <= 10^4$
- $-10^4 <= nums[i] <= 10^4$
nums
为 无重复元素 的 升序 排列数组- $-10^4 <= target <= 10^4$
题解
以下是关于题目 “35. 搜索插入位置” 的C++题解,使用二分查找实现以满足 $O(\log n)$ 的时间复杂度:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <vector>
#include <iostream>
class Solution {
public:
int searchInsert(std::vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid; // 找到目标值,返回索引
} else if (nums[mid] < target) {
left = mid + 1; // 在右半部分继续搜索
} else {
right = mid - 1; // 在左半部分继续搜索
}
}
return left; // 返回插入位置
}
};
// 示例测试
int main() {
Solution solution;
std::vector<int> nums = {1, 3, 5, 6};
int target1 = 5;
int target2 = 2;
int target3 = 7;
std::cout << "输入: [1, 3, 5, 6], target = 5, 输出: " << solution.searchInsert(nums, target1) << std::endl;
std::cout << "输入: [1, 3, 5, 6], target = 2, 输出: " << solution.searchInsert(nums, target2) << std::endl;
std::cout << "输入: [1, 3, 5, 6], target = 7, 输出: " << solution.searchInsert(nums, target3) << std::endl;
return 0;
}
解题思路
- 二分查找:
- 使用二分查找的模板实现,逐步缩小搜索范围。
- 如果找到目标值,直接返回其索引。
- 如果未找到目标值,最终的
left
即为目标值应该插入的位置。
- 复杂度分析:
- 时间复杂度:$O(\log n)$,因为是二分查找。
- 空间复杂度:$O(1)$,只使用了常量空间。
示例输出
运行代码将输出:
1
2
3
输入: [1, 3, 5, 6], target = 5, 输出: 2
输入: [1, 3, 5, 6], target = 2, 输出: 1
输入: [1, 3, 5, 6], target = 7, 输出: 4
74. 搜索二维矩阵
问题描述
给你一个满足下述两条属性的 m x n
整数矩阵:
- 每行中的整数从左到右按非严格递增顺序排列。
- 每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target
,如果 target
在矩阵中,返回 true
;否则,返回 false
。
示例 1:
1
2
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true
示例 2:
1
2
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 100
- $-10^4 <= matrix[i][j], target <= 10^4$
题解
以下是解决“74. 搜索二维矩阵”问题的 C++ 题解,利用二维矩阵的性质,将其看作一个按行展开的有序一维数组,并使用二分查找:
解题代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <vector>
#include <iostream>
class Solution {
public:
bool searchMatrix(std::vector<std::vector<int>>& matrix, int target) {
if (matrix.empty() || matrix[0].empty()) return false;
int m = matrix.size();
int n = matrix[0].size();
int left = 0, right = m * n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
int midValue = matrix[mid / n][mid % n]; // 转换为二维矩阵的元素
if (midValue == target) {
return true; // 找到目标值
} else if (midValue < target) {
left = mid + 1; // 搜索右半部分
} else {
right = mid - 1; // 搜索左半部分
}
}
return false; // 未找到目标值
}
};
// 示例测试
int main() {
Solution solution;
std::vector<std::vector<int>> matrix = {
{1, 3, 5, 7},
{10, 11, 16, 20},
{23, 30, 34, 60}
};
int target1 = 3;
int target2 = 13;
std::cout << "输入: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3, 输出: "
<< (solution.searchMatrix(matrix, target1) ? "true" : "false") << std::endl;
std::cout << "输入: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13, 输出: "
<< (solution.searchMatrix(matrix, target2) ? "true" : "false") << std::endl;
return 0;
}
解题思路
二维矩阵映射到一维数组:
对于矩阵的第
i
行第j
列元素matrix[i][j]
:- 在一维数组中的索引为 $\text{index} = i \times n + j$。
反之,根据索引 $index$
找到矩阵中的元素:
- 行号:$i = \text{index} / n$
- 列号:$j = \text{index} \% n$
二分查找:
- 将矩阵视为一个一维数组,在范围
[0, m*n - 1]
上进行二分查找。 - 每次取中间值,通过索引转换计算出对应的矩阵元素。
- 将矩阵视为一个一维数组,在范围
复杂度分析:
- 时间复杂度:$O(\log(m \cdot n))$,二分查找的时间复杂度。
- 空间复杂度:$O(1)$,只使用了常量空间。
示例输出
运行代码将输出:
1
2
输入: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3, 输出: true
输入: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13, 输出: false
34. 在排序数组中查找元素的第一个和最后一个位置
问题描述
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
1
2
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
1
2
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
1
2
输入:nums = [], target = 0
输出:[-1,-1]
提示:
- $0 <= nums.length <= 10^5$
- $-10^9 <= nums[i] <= 10^9$
nums
是一个非递减数组- $-10^9 <= target <= 10^9$
题解
下面是解决 “34. 在排序数组中查找元素的第一个和最后一个位置” 的 C++ 题解,使用二分查找实现,满足 $O(\log n)$ 的时间复杂度:
解题代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <vector>
#include <iostream>
class Solution {
public:
std::vector<int> searchRange(std::vector<int>& nums, int target) {
std::vector<int> result = {-1, -1}; // 默认返回 [-1, -1]
int left = findBound(nums, target, true); // 查找第一个位置
if (left == -1) return result; // 没找到目标值
int right = findBound(nums, target, false); // 查找最后一个位置
return {left, right};
}
private:
// 查找目标值的边界,isFirst为true时查找第一个位置,false时查找最后一个位置
int findBound(const std::vector<int>& nums, int target, bool isFirst) {
int left = 0, right = nums.size() - 1, result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
result = mid;
if (isFirst) {
right = mid - 1; // 继续查找左边界
} else {
left = mid + 1; // 继续查找右边界
}
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
};
// 示例测试
int main() {
Solution solution;
std::vector<int> nums1 = {5, 7, 7, 8, 8, 10};
int target1 = 8;
std::vector<int> nums2 = {5, 7, 7, 8, 8, 10};
int target2 = 6;
std::vector<int> nums3 = {};
int target3 = 0;
auto result1 = solution.searchRange(nums1, target1);
auto result2 = solution.searchRange(nums2, target2);
auto result3 = solution.searchRange(nums3, target3);
std::cout << "输入: nums = [5,7,7,8,8,10], target = 8, 输出: [" << result1[0] << "," << result1[1] << "]" << std::endl;
std::cout << "输入: nums = [5,7,7,8,8,10], target = 6, 输出: [" << result2[0] << "," << result2[1] << "]" << std::endl;
std::cout << "输入: nums = [], target = 0, 输出: [" << result3[0] << "," << result3[1] << "]" << std::endl;
return 0;
}
解题思路
- 二分查找:
- 我们将问题分为两个子任务:查找目标值的第一个位置和最后一个位置。
- 使用一个辅助函数
findBound
来完成这个任务。通过二分查找,我们可以根据isFirst
参数来决定是否查找第一个位置或最后一个位置。
- 处理边界情况:
- 如果数组为空,返回
[-1, -1]
。 - 在找到目标值时,如果是查找第一个位置,我们继续向左查找;如果是查找最后一个位置,我们继续向右查找。
- 如果数组为空,返回
- 时间复杂度:
- 由于每次二分查找都能将搜索范围缩小一半,时间复杂度为 $O(\log n)$。
- 空间复杂度:
- 只使用了常量空间,空间复杂度为 $O(1)$。
示例输出
运行代码将输出:
1
2
3
输入: nums = [5,7,7,8,8,10], target = 8, 输出: [3,4]
输入: nums = [5,7,7,8,8,10], target = 6, 输出: [-1,-1]
输入: nums = [], target = 0, 输出: [-1,-1]
33. 搜索旋转排序数组
问题描述
整数数组 nums
按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= k < nums.length
)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7]
在下标 3
处经旋转后可能变为 [4,5,6,7,0,1,2]
。
给你 旋转后 的数组 nums
和一个整数 target
,如果 nums
中存在这个目标值 target
,则返回它的下标,否则返回 -1
。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
1
2
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
1
2
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
1
2
输入:nums = [1], target = 0
输出:-1
提示:
1 <= nums.length <= 5000
- $-10^4 <= nums[i] <= 10^4$
nums
中的每个值都 独一无二- 题目数据保证
nums
在预先未知的某个下标上进行了旋转 - $-10^4 <= target <= 10^4$
题解
以下是解决 “33. 搜索旋转排序数组” 的 C++ 题解,利用二分查找来处理旋转排序数组,满足 $O(\log n)$ 的时间复杂度:
解题代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <vector>
#include <iostream>
class Solution {
public:
int search(std::vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid; // 找到目标值,返回索引
}
// 判断哪一部分是有序的
if (nums[left] <= nums[mid]) {
// 左半部分有序
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1; // target 在左半部分
} else {
left = mid + 1; // target 在右半部分
}
} else {
// 右半部分有序
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1; // target 在右半部分
} else {
right = mid - 1; // target 在左半部分
}
}
}
return -1; // 没有找到目标值
}
};
// 示例测试
int main() {
Solution solution;
std::vector<int> nums1 = {4, 5, 6, 7, 0, 1, 2};
int target1 = 0;
std::vector<int> nums2 = {4, 5, 6, 7, 0, 1, 2};
int target2 = 3;
std::vector<int> nums3 = {1};
int target3 = 0;
std::cout << "输入: nums = [4,5,6,7,0,1,2], target = 0, 输出: " << solution.search(nums1, target1) << std::endl;
std::cout << "输入: nums = [4,5,6,7,0,1,2], target = 3, 输出: " << solution.search(nums2, target2) << std::endl;
std::cout << "输入: nums = [1], target = 0, 输出: " << solution.search(nums3, target3) << std::endl;
return 0;
}
解题思路
- 旋转数组的性质:
- 旋转后的数组仍然保持了部分有序的性质。在数组的某个位置,数组被分为两部分:左部分是有序的,右部分也是有序的。
- 二分查找的修改:
- 在普通的二分查找中,我们只需直接比较
nums[mid]
和target
,判断目标值的大小。 - 对于旋转数组,我们需要判断哪一部分是有序的:
- 如果左半部分有序(即
nums[left] <= nums[mid]
),我们就能判断target
是否在左半部分,进一步更新left
和right
的值。 - 如果右半部分有序(即
nums[mid] < nums[right]
),则同理判断target
是否在右半部分。
- 如果左半部分有序(即
- 在普通的二分查找中,我们只需直接比较
- 复杂度分析:
- 时间复杂度:$O(\log n)$,因为我们仍然在每次循环中将搜索范围减半。
- 空间复杂度:$O(1)$,只使用了常量空间。
示例输出
运行代码将输出:
1
2
3
输入: nums = [4,5,6,7,0,1,2], target = 0, 输出: 4
输入: nums = [4,5,6,7,0,1,2], target = 3, 输出: -1
输入: nums = [1], target = 0, 输出: -1
153. 寻找旋转排序数组中的最小值
问题描述
已知一个长度为 n
的数组,预先按照升序排列,经由 1
到 n
次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
给你一个元素值 互不相同 的数组 nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
1
2
3
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
1
2
3
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。
示例 3:
1
2
3
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
提示:
n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums
中的所有整数 互不相同nums
原来是一个升序排序的数组,并进行了1
至n
次旋转
题解
以下是解决“153. 寻找旋转排序数组中的最小值”问题的 C++ 题解,使用二分查找实现 $O(\log n)$ 的时间复杂度:
解题代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <vector>
#include <iostream>
class Solution {
public:
int findMin(std::vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
// 如果中间值比右边界大,说明最小值在右半部分
if (nums[mid] > nums[right]) {
left = mid + 1;
} else {
// 如果中间值小于等于右边界,最小值在左半部分或就是 mid
right = mid;
}
}
// 最小值在 left 位置
return nums[left];
}
};
// 示例测试
int main() {
Solution solution;
std::vector<int> nums1 = {3, 4, 5, 1, 2};
std::vector<int> nums2 = {4, 5, 6, 7, 0, 1, 2};
std::vector<int> nums3 = {11, 13, 15, 17};
std::cout << "输入: nums = [3,4,5,1,2], 输出: " << solution.findMin(nums1) << std::endl;
std::cout << "输入: nums = [4,5,6,7,0,1,2], 输出: " << solution.findMin(nums2) << std::endl;
std::cout << "输入: nums = [11,13,15,17], 输出: " << solution.findMin(nums3) << std::endl;
return 0;
}
解题思路
- 数组特性:
- 旋转后的数组由两个升序区间组成,其中最小值出现在分界点。
- 如果
nums[mid] > nums[right]
,说明最小值在右半部分。 - 如果
nums[mid] <= nums[right]
,说明最小值在左半部分或就是mid
。
- 二分查找:
- 使用二分查找逐步缩小搜索范围。
- 当
left == right
时,区间被缩小到只剩一个元素,该元素即为最小值。
- 复杂度分析:
- 时间复杂度:$O(\log n)$,因为每次循环都将搜索范围缩小一半。
- 空间复杂度:$O(1)$,仅使用了常量空间。
示例输出
运行代码将输出:
1
2
3
输入: nums = [3,4,5,1,2], 输出: 1
输入: nums = [4,5,6,7,0,1,2], 输出: 0
输入: nums = [11,13,15,17], 输出: 11
4. 寻找两个正序数组的中位数
问题描述
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n))
。
示例 1:
1
2
3
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
1
2
3
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
- $-10^6 <= nums1[i], nums2[i] <= 10^6$
题解
解决“4. 寻找两个正序数组的中位数”问题的 C++ 实现,使用二分查找满足 $O(\log(m+n))$ 的时间复杂度:
解题代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <vector>
#include <iostream>
#include <algorithm>
class Solution {
public:
double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
// 确保 nums1 是较短的数组
if (nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.size();
int n = nums2.size();
int totalLeft = (m + n + 1) / 2;
int left = 0, right = m;
while (left <= right) {
int i = left + (right - left) / 2;
int j = totalLeft - i;
int nums1LeftMax = (i == 0) ? INT_MIN : nums1[i - 1];
int nums1RightMin = (i == m) ? INT_MAX : nums1[i];
int nums2LeftMax = (j == 0) ? INT_MIN : nums2[j - 1];
int nums2RightMin = (j == n) ? INT_MAX : nums2[j];
if (nums1LeftMax <= nums2RightMin && nums2LeftMax <= nums1RightMin) {
// 找到合适的分割点
if ((m + n) % 2 == 0) {
return (std::max(nums1LeftMax, nums2LeftMax) +
std::min(nums1RightMin, nums2RightMin)) / 2.0;
} else {
return std::max(nums1LeftMax, nums2LeftMax);
}
} else if (nums1LeftMax > nums2RightMin) {
right = i - 1; // i 太大
} else {
left = i + 1; // i 太小
}
}
throw std::invalid_argument("Input arrays are not valid.");
}
};
// 示例测试
int main() {
Solution solution;
std::vector<int> nums1_1 = {1, 3};
std::vector<int> nums1_2 = {2};
std::cout << "输入: nums1 = [1,3], nums2 = [2], 输出: "
<< solution.findMedianSortedArrays(nums1_1, nums1_2) << std::endl;
std::vector<int> nums2_1 = {1, 2};
std::vector<int> nums2_2 = {3, 4};
std::cout << "输入: nums1 = [1,2], nums2 = [3,4], 输出: "
<< solution.findMedianSortedArrays(nums2_1, nums2_2) << std::endl;
return 0;
}
解题思路
- 分割数组:
- 假设在
nums1
和nums2
的合并数组中,找到分割点,使得左侧元素总数等于右侧元素总数(或左侧比右侧多一个)。 - 分割点将数组划分为两个部分,左侧最大值和右侧最小值用于计算中位数。
- 假设在
- 二分查找:
- 将问题转化为在较短的数组
nums1
上进行二分查找。 - 令分割点为
i
,nums2
的分割点为j = (m + n + 1) / 2 - i
。 - 根据条件调整
i
的范围,找到满足:nums1[i - 1] <= nums2[j]
nums2[j - 1] <= nums1[i]
- 将问题转化为在较短的数组
- 边界处理:
- 如果
i == 0
或i == m
,对应的边界使用INT_MIN
或INT_MAX
处理。
- 如果
- 时间复杂度:
- 二分查找的时间复杂度为 $O(\log(\min(m, n)))$。
- 空间复杂度为 $O(1)$。
示例输出
运行代码将输出:
1
2
输入: nums1 = [1,3], nums2 = [2], 输出: 2
输入: nums1 = [1,2], nums2 = [3,4], 输出: 2.5