二分查找 & 移除元素
二分查找
给定一个n个元素有序(升序)整型数组nums和一个目标值 target ,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1。
主要会在哪里出问题呢?
一般是对于区间是左闭右开,还是左闭右闭上容易出现问题。
先来看左闭右开,其实思路是比较简单的,因为数组是有序的,我们需要抓住头尾left、right,首先判断区间的有效性,区间是这样的:$[0,n)$,如果说区间只有一个元素1,[1,1),这是不合法的,所以while判断里面应该写left<right;然后是查找的逻辑,如果target小于中心点元素,那么目标值就在左边,right = middle,因为nums[middle]这个值显然不是目标值的索引,而右边区间是开区间,是取不到的,没有问题,但是反过来,如果目标值在右边,那么,left = middle+1了,因为middle索引不是目标值,即使left=middle,那么判断的时候取到左闭的值就多余了;如果找到了,直接返回middle索引。
// 左闭右开
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while(left < right){
int middle = (left + right) / 2;
if(target < nums[middle]){
right = middle;
} else if(target > nums[middle]){
left = middle + 1;
} else {
return middle;
}
}
return -1;
}
};
// 左闭右闭
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while(left <= right){
int middle = (left + right) / 2;
if(target < nums[middle]){
right = middle - 1;
} else if(target > nums[middle]){
left = middle + 1;
} else {
return middle;
}
}
return -1;
}
};
移除元素
给你一个数组 nums 和一个值val,你需要移除所有数值等于 val 的元素,然后返回新数组的大小。
这题其实在之前编写游戏代码中遇到过很多次了,一个vector数组,删除特定的元素,我们会采用erase-remove惯用法来对数组进行操作。
数组是一段连续的内存存储的,不能直接删除,一般是会做一个覆盖,然后通过计数器进行删除。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
auto it = std::remove(nums.begin(), nums.end(), val);
nums.erase(it,nums.end());
return nums.size();
}
};
如果这样就结束了,那真是太逊了。
如果一个数组[1,2,3,3,5],我们要删除元素3,我们不能直接删,需要做一个覆盖,我们似乎可以这样,for循环找到要删的元素,找到了,然后继续for循环后面的元素,把元素往前面放这样就行了。(O(n2))
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for(int i=0;i<size;++i){
if(nums[i] == val){
for(int j=i+1;j<size;++j){
nums[j-1] = nums[j];
}
/* 找到后,需要执行,比如[1,2,3,3,4],删除3,那么nums[2]的时候,
找到了,这个时候要把2后面的元素做覆盖,完毕后,现在依然是从2索引开始,
因为原本2索引的位置被覆盖了,不知道覆盖后的元素还满不满足要求,
当然数组大小size需要减1,这好理解,我们覆盖了一个旧的元素
*/
--i;
--size;
}
}
return size;
}
};
双指针实现,比如一个数组[1,2,3,4,3,5],删除3这个元素。删除3后得到的元素就是新数组需要的元素。
两个指针(变量)以不同的 “速度” 遍历数组—— 快指针负责 “探路 / 遍历所有元素”,慢指针负责 “标记有效元素的位置”。还是容易理解的
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for(int fast = 0; fast < nums.size(); ++fast){
if(nums[fast] != val){
nums[slow] = nums[fast];
++slow;
}
}
return slow;
}
};
nums[slow] = nums[fast]; ++slow;
这两行可以改写成nums[slow++] = nums[fast];
++slow:先自增,后使用;slow++:先使用,后自增(会产生零时变量);前置先加后用,后置先用后加
类似逻辑
// 模拟前置自增的行为(伪代码)
int& operator++() { // 返回原变量的引用(左值)
slow = slow + 1; // 第一步:先修改原变量
return slow; // 第二步:返回修改后的原变量(引用)
}
// 模拟后置自增的行为(伪代码)
int operator++(int) { // 参数int是标记,返回临时值(右值)
int temp = slow; // 第一步:保存旧值到临时变量
slow = slow + 1; // 第二步:修改原变量
return temp; // 第三步:返回旧值(临时变量)
}

浙公网安备 33010602011771号