二分查找是最为常用的算法,仅仅看基础的二分算法似乎会觉得很简单,可以说是最为简单的算法也不为过。但在实际应用中做到灵活应用并且深刻理解说实话还是需要认真思考的。
利用二分查找算法可以高效的解决问题,可以将时间复杂度从 O(n) 降为 O(log(n)),特别对于较大数据量处理中具有非常显著的性能提升。
基本二分查找 LeetCode#704
基本的二分查找示例可以参考 https://leetcode.cn/problems/binary-search/
这里采用两种常用的方法来展示, 方法一中区间为 [left, right) 左闭右开区间,所以右端区间的初始态取数组的大小。在循环过程中只有当 left < right 时才进行循环;在区间缩放过程中,右端区间需要改为 right = mid。
// 方法一
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
int ret = -1;
// 这里采用<,表示区间范围是 [left, right),所以对应的区间
// 进行右边缩放时需要注意
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
}
else if (nums[mid] > target) {
right = mid;
}
else {
ret = mid;
break;
}
}
return ret;
}
方法二中区间为 [left, right] 左闭右闭区间,所以右端区间的初始态取数组的大小减一 nums.size() - 1。在循环过程中只有当 left <= right 时才进行循环;在区间缩放过程中,右端区间需要改为 right = mid - 1。
// 方法二
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int ret = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
}
else if (nums[mid] > target) {
right = mid - 1;
}
else {
ret = mid;
break;
}
}
return ret;
}
双端二分查找 LeetCode#34
这道题目可以用来熟悉二分查找的应用。需要进行两次二分查找分别找到最左边 target 和最右边的 target,从而返回对应的 index。
因为需要找到 target 的最左端值和最右端值,当找到 mid 值等于 target 时需要做出相应的区间缩放操作。
如果区间本身仅仅有一个元素的时候需要注意索引的范围,防止索引越界问题发生。
查找右端 target 的时候需要注意最后结束时返回 left 的值,需要进行 left - 1 操作。
class Solution {
public:
int leftTarget(const vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
}
else if (nums[mid] > target) {
right = mid;
}
else if (nums[mid] == target) {
right = mid;
}
}
if (right != nums.size() && nums[right] == target) {
return right;
}
return -1;
}
int rightTarget(const vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
int ret = -1;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
}
else if (nums[mid] > target) {
right = mid;
}
else if (nums[mid] == target) {
left = mid + 1;
}
}
if (left - 1 >= 0 && nums[left - 1] == target) {
return left - 1;
}
return -1;
}
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ret_vec{ -1, -1 };
if (nums.empty()) {
return ret_vec;
}
ret_vec = { leftTarget(nums, target), rightTarget(nums, target) };
return ret_vec;
}
};
二分查找搜索插入位置 LeetCode#35
该题目中需要在一个有序的数组中查找目标值,如果仅仅是该需求的话,利用最为基本的二分就可以完成。但题目中新增一个需求(即当数组中没有目标值时返回需要插入的位置),这就是与基本二分最不同的点。
利用二分查找寻找目标值时,如果没有找到则最终会返回需要插入的位置。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while (left < right) {
int mid = (left + right) >> 1;
if (nums[mid] > target) {
right = mid;
}
else if (nums[mid] < target){
left = mid + 1;
}
else {
//当找到时,可以直接返回,无需等到循环退出。
return mid;
}
}
return left;
}
};
二分求解效率类题目 LeetCode#875
这道题目是二分查找较为成熟的应用题,该类题目当应用二分查找时会大幅度缩短查找时间提高效率,时间复杂度会降为 O(log n)。
题目中相当于给出了工作时间h,以及需要处理的工作总量piles,现在需要计算出工作效率。这类题目可以首先设定出最小和最大的工作效率(也就是区间 [min, max]),依次计算出中间效率,然后根据中间效率和工作总量计算出基于当前状态的工作时间,与题目中给出的工作时间进行比较;然后依据计算出的时间与题目中出现的时间的大小关系,利用二分查找算法对区间 [min, max] 进行缩放调整。
具体的解发如下所示,该解法中将区间的最小值 min_speed 初始化设置为 1,当然也可以依据数组的大小以及给出的h进行计算,但是在二分似乎没有这个必要,因为二分的时间复杂度已经很低了,没必要对区间左值的初始值进行复杂计算来确定。
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int h) {
int min_speed = 1;
int max_speed = 0;
// 计算可能的最大速度值,也就是单个香蕉堆的最大值。
for (auto pile : piles) {
if (pile > max_speed) {
max_speed = pile;
}
}
while (min_speed < max_speed) {
int mid = (min_speed + max_speed) >> 1;
// 当需要的时间值远大于需要处理的工作时,取最小效率也就是1。
if (mid == 0) {
return 1;
}
// 计算在当前的 mid 速度之下,处理完所有待处理任务需要的
// 时间,然后利用二分进行左右缩放进而拿到满足条件的效率值。
int hour = 0;
for (auto p : piles) {
hour += (p + mid - 1) / mid;
}
if (hour <= h) {
max_speed = mid;
}
else {
min_speed = mid + 1;
}
}
return min_speed;
}
};
二分求解效率类题目 LeetCode#1011
这道题目与上数题目是一类题目,都是效率类题目,利用二分查找可以快速的解决问题。
需要特别注意的是:最小值为最大的单个包裹,因为题目中说明了“单个包裹”,也就意外着一个包裹必须一次运送,不可以分批运送。
class Solution {
public:
int shipWithinDays(vector<int>& weights, int days) {
// 最小值为最大的单个包裹,因为题目中说明了“单个包裹”,也就意外着
// 一个包裹必须一次运送,不可以分批运送。
int min_wts = 0;
int max_wts = 0;
for (auto weight : weights) {
max_wts += weight;
if (weight > min_wts) {
min_wts = weight;
}
}
while (min_wts < max_wts) {
int mid = (min_wts + max_wts) >> 1;
int tmp_wts = 0;
int cnt = 0;
for (auto wts : weights) {
tmp_wts += wts;
if (tmp_wts > mid) {
cnt++;
tmp_wts = wts;
}
}
cnt++;
if (cnt <= days) {
max_wts = mid;
}
else {
min_wts = mid + 1;
}
}
return min_wts;
}
};