二分查找
区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
左闭右闭
即[left,right]
while(left<=right)
因为left可以=right
left会变成mid+1,right变成mid-1,因为在
if (nums[middle] > target)
之后,已经排除了mid元素,不包含在区间里。
int search(vector<int>& nums, int target) {
int l = 0, r= nums.size() - 1;
while(l <= r){
int mid = (l + r) / 2;
if(nums[mid] > target) r = mid - 1;
else if(nums[mid] < target) l = mid + 1;
else return mid;
}
return -1;
}
左闭右开
while(left<right)
left不可以等于right
left变成mid+1,right变成mid,
if (nums[middle] > target)
right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
int search(vector<int>& nums, int target) {
int l = 0, r= nums.size();
while(l < r){
int mid = (l + r) / 2;
if(nums[mid] > target) r = mid;
else if(nums[mid] < target) l = mid + 1;
else return mid;
}
return -1;
}
35.搜索插入位置
如果数组里没有target,那么r最终指向的是比target小的最大元素
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
int mid;
while(l <= r){
mid = (l + r) / 2;
if(nums[mid] > target) r = mid - 1;
else if(nums[mid] < target) l = mid + 1;
else return mid;
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right + 1
// 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
return r + 1;
}
34. 在排序数组中查找元素的第一个和最后一个位置
关于是在哪个if条件里设立边界的问题,
只需要关注当target=num[mid]的时候,l或者r更新后是在target的左侧还是右侧,根据这点就知道边界赋值的句子应该写在哪。
对于
nums[mid] >= target
,当相等时,r = mid - 1,r在target左边,且num[r]刚好为小于target的最大数,所以当前的r是左边界。
对于
nums[mid] <= target
,当相等时,l = mid + 1,l在target右边,且num[l]刚好为大于target的最小数,所以l是右边界。
int rightBorder(vector<int>& nums,int target){
int l = 0, r= nums.size() - 1;
int rb = -2;
while(l <= r){
int mid = (l + r) / 2;
if(nums[mid] > target) r = mid - 1;
else{
l = mid + 1;
rb = l;
}
}
return rb;
}
int leftBorder(vector<int>& nums,int target){
int l = 0, r= nums.size() - 1;
int lb = -2;
while(l <= r){
int mid = (l + r) / 2;
if(nums[mid] >= target){
r = mid - 1;
lb = r;
}
else{
l = mid + 1;
}
}
return lb;
}
vector<int> searchRange(vector<int>& nums, int target) {
int lb = leftBorder(nums,target);
int rb = rightBorder(nums,target);
if(lb == -2 || rb == -2) return {-1,-1};
// 因为rb和lb都是不包含target的边界,所以它们之间的间隔必定大于1,否则无法包含target
if(rb - lb > 1) return {lb+1,rb-1};
return {-1,-1};
}
27.移除元素
暴力法
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
for(int i = 0; i< n; i++){
if(nums[i] == val){
for(int j = i+1; j < n; j++){
nums[j-1] = nums[j];
}
// 当前位置已经是下一个元素了,下一个元素也可能是val,所以还需要判断
i--;
n--;
}
}
return n;
}
// 一个for循环遍历数组元素 ,第二个for循环更新数组。
双指针法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
int fast = 0, slow = 0;
for(;fast<n;fast++){
if(nums[fast] != val) nums[slow++] = nums[fast];
}
return slow;
}