Leetcode 704.二分查找
- 左闭右闭
不同的区间就是要对while循环中的判断条件以及right边界做出改变。
因为是左闭右闭,所以right = nums.size() - 1
,即nums[right]
可以取到,且循环条件为while(left <= right)
,因为当right == left
时进入循环时有意义的。 - 左闭右开
因为是左闭右开,所以right = nums.size()
,即取不到边界值,那么同理当right == left
时进入循环时没有意义。
下面给出左闭右开的代码:
int left = 0;
int right = nums.size();
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;
}
return -1;
Leetcode 35.搜索插入位置
本题与704唯一不同的地方就是当target在递增数组范围内却找不到时需要插入。
需要注意的点有两个:
- 当元素在数组中间位置插入时,这里返回最后一次的
mid
值即可 - 当元素在数组边界位置插入时,尤其是右边界,这种情况需要增加数组的容量才能插入新的元素,所以采用左闭右开的二分法。当循环结束时返回值为原数组长度,相当于新插入的索引。
下面给出代码:
int left = 0;
int right = nums.size();
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;
}
return left + (right - left)/2;
Leetcode 34.在排序数组中查找元素的第一个和最后一个位置
- 本题如果采用暴力解法的思路就是,先正序遍历数组找到第一个target出现的位置,再倒序遍历数组找到最后一个target出现的位置。
下面给出代码:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = -1;
int right = -1;
for(int i = 0; i < nums.size(); i++){
if(nums[i] > target) break;
if(nums[i] == target){
left = i;
break;
}
else continue;
}
for(int i = nums.size() - 1; i >= 0; i--){
if(nums[i] < target) break;
if(nums[i] == target){
right = i;
break;
}
else continue;
}
return {left,right};
}
};
- 但是题目要求时间复杂度为
O(log n)
,所以想到二分法找边界,本题使用左闭右闭的二分法。二分法思路和暴力差不多,但是在算法上做了优化,并且根据卡哥的提示,分开写左右边界的寻找,本题在细节上还是有很多需要注意的地方。
- 首先就是确定在二分的时候,何时更新左右边界,以右边界为例。
寻找右边界,需要用左边界不断逼近使循环结束时,left所处位置为target的下一个不等于target的元素。
所以当target < nums[mid]
时是不需要更新右边界的,因为此时不用更新二分中的左边界,同时要找到target元素的边界,在target == nums[mid]
时也是要更新的。下面给出代码:
int left = 0;
int right = nums.size() - 1;
int rightboarder = -2;
while(left <= right){
int mid = left + (right - left)/2;
if(nums[mid] > target){
right = mid - 1;
}else{
left = mid + 1;
rightboarder = left;
}
}
return rightboarder;
左边界同理,使用二分的右边界来不断逼近,使结束循环时nums[right]
位于target元素的左边一个位置。最后综合两者来完成主函数。
- 卡哥在寻找左右边界前先讨论了三种情况,这三种情况下的判断条件也是细节之一。
int leftboarder = searchleft(nums, target);
int rightboarder = searchright(nums, target);
//这里是或不是与
if(leftboarder == -2 || rightboarder == -2) return {-1,-1};
if(rightboarder - leftboarder > 1) return {leftboarder + 1 , rightboarder - 1};
return {-1 , -1};
结合寻找左右边界时的思路,就可以写出当target存在于数组时的边界,因为找出的边界是target两侧前一个或后一个元素,所以输出的时候需要对应的+1和-1。另外一个点就是只要左右边界有任意一个不满足条件,那么两者都不满足target在数组中的条件。
Leetcode 27.移除元素
双指针法是本题的精髓。本题相当于是对vector中erase方法的实现,其中慢指针是指向新数组中需要更新的位置,快指针指向新数组的元素。所以当遇到需要删除(覆盖)的元素时,慢指针不动,快指针继续移动;当慢指针当前指向的元素不是需要删除的元素时,慢指针正常移动。
代码如下:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0;
for(int right = 0; right < nums.size(); right++){
if(nums[right] == val) continue;
nums[left] = nums[right];
left++;
}
return left;
}
};
额外的知识点
在二分法计算mid
时,会有一个操作:mid = left + (right - left)/2
,这样是防止mid
值的溢出,向群里大佬学习也可以用移位操作来实现。
这里搬运记录一下:
- 当n为负数时,
>> 1
和/2
的结果是一样的 - 当n为负数且为偶数时,
>> 1
和/2
的结果是一样的 - 当n为负数且为奇数时,
>> 1
和/2
的结果是不一样的
因为奇数除以二会发生截断现象,而此时>> 1
和/2
截断的方向不一样,导致结果不同。
如:-5 / 2 = -(int)2.5 = -2
而-5 >> 1 = (1011) >> 1 = (1101) = -3
。