二分查找 & 移除元素

二分查找

给定一个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;      // 第三步:返回旧值(临时变量)
}
posted @ 2026-03-03 16:16  wenyiGamecpp  阅读(18)  评论(0)    收藏  举报