二分法的使用以及边界设定
二分法用于有序序列的查找,其平均复杂度为O(lg(n)),二分法的算法比较简单,但是其边界的处理却让人很头疼,为方便以后复习,下面总结出了4中目前我遇到的模板
二分法的几个要素:
1、left
2、mid
3、right
一般我们要查找的结果也是这三个中的其中一个,目前我遇到的是第一种和第三种
题目背景:
找出一个有序序列中某元素第一次或最后一次出现的位置
显然序列中某个元素可能存在多个,下面以 arr = [1,2,3,3,3,3,5]这个序列为例
找第一次出现的位置
整体思路:
1、 当mid 处的值明确小于目标值时,将左边界右移 即arr[mid] < target 时 left = mid + 1
2、当 mid 处的值大于目标值时,将右边界左移 即arr[mid] > target时, right = mid
3、 目标是找到某元素第一次出现的位置,若在某个mid位置的值刚好是目标元素,
重点来了:
接下来要做的就是看当前元素前面有没有与目标元素一样的,有可能有,也有可能没有,所以要保留当前mid, 这种情况其实就是查找范围要往左边缩减,即
arr[mid] >= target时 right = mid
4、最终 left 和 right 相等,循环结束,left就是要找的位置
模板一: 查找left
int binarySearchFirst(vector<int> arr, int target) {
int left = 0;
int right = arr.size() - 1;
int mid;
//此处的while为关键
while (left < right) {
mid = left + (right - left) / 2;
if (arr[mid] < target) left = mid + 1; //因为已经明确mid 处的值明确小于目标值时,所以目标一定在[mid+1,right]中
else right = mid;
}
//还要判断left的值是否真的是target,即判断目标是否存在
if (arr[left] == target) return left;
else return -1;
}
while循环内取 left<right 而不是 left<=right ,是因为若 取到等号会陷入死循环
模板二: 查找right
int binarySearchFirst(vector<int> arr, int target) {
int left = 0;
int right = arr.size() - 1;
int mid;
//此处的while为关键
while (left + 1 < right) {
mid = left + (right - left) / 2;
if (arr[mid] < target) left = mid ;
else right = mid;
}
//还要判断right的值是否真的是target,即判断目标是否存在
//这种情况就变成了取右值
if (arr[right] == target) return left;
else return -1;
}
找最后一次出现的位置
整体思路:
1、 mid 处的值明确大于目标值时,将右边界左移
2、 当 mid 处的值小于目标值时,将左边界右移
3、 当mid处的值等于目标值时,需要继续观察mid后面有没有等于目标值的地方,即
arr[mid]<=target left = mid
4、left 和 right 相等,循环终止,该位置就是目标值最后一次出现的位置。
模板三:找left
public static void binarySearchLast(vetor<int> arr, int target) {
int left = 0;
int right = arr.size() - 1;
int mid;
while (left < right) {
mid = left + (right - left + 1) / 2; //+1是为了向上取整,mid 会偏向右边界,因此 left = mid 可以确保左边界往右移动,缩小查找范围。
if (arr[mid] > t) right = mid - 1;
else left = mid;
}
if (arr[left] == target) return left;
else return -1;
}
模板四:找left
public static void binarySearchLast(vetor<int> arr, int target) {
int left = 0;
int right = arr.size() - 1;
int mid;
while (left + 1 < right) {
mid = left + (right - left + 1) / 2; //+1是为了向上取整,mid 会偏向右边界,因此 left = mid 可以确保左边界往右移动,缩小查找范围。
if (arr[mid] > t) right = mid;
else left = mid;
}
if (arr[left] == target) return left;
else return -1;
}
这几种模板可能对有些题目不起作用(具体还没分析过),但弄明白这几种,其他的也可以熟悉的分析出来
附上LeetCode875. 爱吃香蕉的珂珂的C++代码
方法一:
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int H) {
int r = pow(10,9);
int l = 0, mid;
while (left + 1 < right) {
mid = left +(right - left) / 2;
if (f(piles, H, mid)) {
left = mid;
} else {
right = mid;
}
}
return right;
}
bool f(vector<int>& piles, int H, int m) {
int t = 0;
for (auto p : piles) {
t += p / m + (p % m != 0);
if (t > H) return true;
}
return false;
}
};
方法二:
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int H) {
int left = 1;
int right = pow(10, 9);
int mid;
while (left < right) {
mid = left + (right - left) / 2;
if (findKey(piles, H, mid)) {
left = mid + 1;
}
else {
right = mid;
}
}
return left;
}
bool findKey(vector<int>&piples, int H, int mid) {
int t = 0;
for (auto pile : piples) {
t += pile / mid + (pile % mid == 0 ? 0 : 1);
}
if (t <= H) {
return false;
}
return true;
}
};