框架套路
Leetcode 704. 二分查找
这类型的题是有框架的。需要记清楚的是while的判断条件和各个if情况下是否+1
第一种:最基本的二分查找算法
因为我们初始化 right = nums.length - 1
所以决定了我们的「搜索区间」是[left, right]
所以决定了 while (left <= right)
同时也决定了 left = mid+1
和 right = mid-1
因为我们只需找到一个 target 的索引即可
所以当 nums[mid] == target
时可以立即返回
第二种:寻找左侧边界的二分查找
因为我们初始化 right = nums.length-1
所以决定了我们的「搜索区间」是 [left, right]
所以决定了 while (left <= right)
同时也决定了 left = mid + 1
和 right = mid-1
因为我们需找到 target 的最左侧索引
所以当 nums[mid] == target
时不要立即返回
而要收紧右侧边界以锁定左侧边界,即right = mid - 1;
最后还要判断return nums[left] == target ? left : -1;
第三种:寻找右侧边界的二分查找
因为我们初始化 right = nums.length-1
所以决定了我们的「搜索区间」是 [left, right]
所以决定了 while (left <= right)
同时也决定了 left = mid + 1
和 right = mid-1
因为我们需找到 target 的最右侧索引
所以当 nums[mid] == target
时不要立即返回
而要收紧左侧边界以锁定右侧边界
又因为收紧左侧边界时必须 left = mid + 1
最后还要判断return nums[right] == target ? right: -1;
对于我个人,我比较喜欢左右闭区间的写法
int binary_search(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) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
}
int left_bound(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) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 判断 target 是否存在于 nums 中
// 此时 target 比所有数都大,返回 -1
if (left == nums.size()) return -1;
// 判断一下 nums[left] 是不是 target
return nums[left] == target ? left : -1;
}
int right_bound(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) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定右侧边界
left = mid + 1;
}
}
// 此时 left - 1 索引越界
if (left - 1 < 0) return -1;
// 判断一下 nums[left] 是不是 target
return nums[left - 1] == target ? (left - 1) : -1;
}
还有一种逆向思维的框架:
这个在153、154题会出现。
class Solution {
public:
int searchInsert(vector<int>& nums, target) {
int n = nums.size();
int l = 0, r = n - 1;
while(l < r) { // 注意此时不能有等于了,因为我们这里是排除不可能区间
int mid = (l+r)/2;
if(nums[mid] < target) // 这里不能判断等于的情况,因为我们用的排除思维,只需要排除目标一定不在的元素区间
l = mid + 1;
else r = mid; // 这是nums[mid] >= target的情况,说明目标在mid及左边, 往左缩小
}
return nums[l] == target ? nums[l] : -1; // 退出循环,要么找到,要么没找到,如果找到的话,left和right都指向它了
}
};
完全有序
只要题目给出的数组是有序的,就要第一时间想到用二分法。
Leetcode 35. 搜索插入位置
Leetcode 34. 在排序数组中查找元素的第一个和最后一个位置
剑指 Offer 53 - I. 在排序数组中查找数字 I
剑指 Offer 53 - II. 0~n-1中缺失的数字
Leetcode 69. x 的平方根
Leetcode 367. 有效的完全平方数
不完全有序
旋转数组是部分有序,部分无序,也就是说确定了mid之后,mid的左边和右边是一个有序和一个无序的,所以我们确定了mid之后,应该马上确定mid的那一边是有序的,哪一边是无序的,再来确定下一个搜索空间(left和right的值)。
Leetcode 33. 搜索旋转排序数组
Leetcode 81. 搜索旋转排序数组 II
Leetcode 153. 寻找旋转排序数组中的最小值
Leetcode 154. 寻找旋转排序数组中的最小值 II
关于这4个题,反反复复做,人都给我绕晕了,我进行总结一下。
首先是前2题,while的条件为(l <= r),因为要找到target,所以必须在while中包含l==r
的时候的情况;
然后是后2题,只能和右边界比较,while的条件为(l < r),因为我们并不需要找到某个确切的值,所以需要有出while的条件,即l== r
的时候出。
最后就是关于重复值的问题,154使用if(nums[r] == nums[mid]) r--;
,81使用if(nums[l] == nums[mid] l++;
死记硬背了,嗐。
二维数组
Leetcode 74. 搜索二维矩阵
Leetcode 240. 搜索二维矩阵 II
其他
Leetcode 162. 寻找峰值
Leetcode 875. 爱吃香蕉的珂珂
Reference
【Leetcode 专题四】二分法