二分查找
二分查找的本质就是 缩小有效范围
需要注意:
- int mid = (left + right) / 2;
int mid = left + (right - left) / 2;
防止溢出
hot100 - 二分查找
⭐️35. 搜索插入位置
找到第一个大于等于 target 的值
cpp
class Solution {
public:
// 目标: 找到第一个大于等于 target 的值
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) { // 区间不为空
// 区间缩减的循环不变量
// nums[left - 1] < target
// nums[right + 1] >= target
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1; // 缩小范围到 [mid + 1, right]
} else {
right = mid - 1; // 缩小范围到 [left, mid - 1]
}
}
return left;
}
};
cpp
class Solution {
public:
// 目标: 找到第一个大于等于 target 的值
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) { // 区间不为空
// 区间缩减的循环不变量
// nums[left - 1] < target
// nums[right] >= target
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1; // 缩小范围到 [mid + 1, right]
} else {
right = mid; // 缩小范围到 [left, mid - 1]
}
}
return left;
}
};
74. 搜索二维矩阵
cpp
class Solution {
public:
// 二分法的核心:缩小有效区间
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n = matrix.size(), m = matrix[0].size();
if (target < matrix[0][0] || target > matrix[n - 1][m - 1]) return false;
int left = 0, right = n - 1, row = 0, col = 0;
// 二分查找,先确定在哪一行
while (left <= right) {
// 循环不变量
// nums[left-1][m-1] < target
// nums[right+1][m-1] > target
row = left + (right - left) / 2;
if (matrix[row][m - 1] == target) {
return true;
} else if (matrix[row][m - 1] < target) {
left = row + 1;
} else {
right = row - 1;
}
}
// 循环结束
row = left;
// 二分查找,再确定在哪一列
left = 0, right = m - 1;
while (left <= right) {
// 循环不变量
// nums[row][left-1] < target
// nums[row][right+1] > target
col = left + (right - left) / 2;
if (matrix[row][col] == target) {
return true;
} else if (matrix[row][col] < target) {
left = col + 1;
} else {
right = col - 1;
}
}
return false;
}
};
⭐️⭐️⭐️162. 寻找峰值
cpp
class Solution {
public:
// 首先我们可以判定,数组中必然包含至少一个峰值元素
// 峰值元素的特征 nums[i] > nums[i-1] nums[i] > nums[i + 1]
// 相当于找极大值点
int findPeakElement(vector<int>& nums) {
int n = nums.size();
int left = 0, right = n - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if ((mid == 0 || nums[mid] > nums[mid - 1]) && (mid != n - 1 && nums[mid] < nums[mid + 1])) {
left = mid + 1;
} else if ((mid != 0 && nums[mid] < nums[mid - 1]) && (mid == n - 1 || nums[mid] > nums[mid + 1])) {
right = mid - 1;
} else if ((mid == 0 || nums[mid] > nums[mid - 1]) && (mid == n - 1 || nums[mid] > nums[mid + 1])) {
return mid;
} else {
// 以下两种都可以
left = mid + 1;
// right = mid - 1;
}
}
return left;
}
};
代码优化
严格证明,简洁写法(Python/Java/C++/C/Go)
cpp
class Solution {
public:
// 首先我们可以确定必然有一个峰值, 因此我们只需要进行区间缩减即可
int findPeakElement(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[mid + 1]) { // 如果 nums[mid] < nums[mid + 1],说明右侧有峰值
left = mid + 1;
} else { // nums[mid] > nums[mid + 1], 说明左侧有峰值
right = mid;
}
}
// left == right 时即为峰值位置
return left;
}
};
33. 搜索旋转排序数组
看清楚题意,是将一个升序序列旋转一下,不是两个升序序列拼在一起
cpp
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size(), left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) {
return mid;
} else {
if (nums[mid] >= nums[0]) { // 在第一个上升区间 nums[mid] >= nums[0]
if (target > nums[mid] || target < nums[0]) left = mid + 1;
else right = mid - 1;
} else { // 在第二个上升区间 nums[mid] < nums[0]
if (target < nums[mid] || target > nums[n - 1]) right = mid - 1;
else left = mid + 1;
}
}
}
return -1;
}
};