27. 移除元素(C++)

题目

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

分析与题解

循环嵌套暴力解

图片

思路很清晰,我们先循环遍历一个数组,当查询到目标值元素后,再开一个循环把该下标后面的所有元素向前移位依次赋值。

因为题目说明不需要考虑返回长度外的数组元素,所以可以略过

完整代码如下:

class Solution {
public:
    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];
                }
                // 可以立即改变外层循环的边界值
                n--;
                // 因为目标元素后的所有元素全部向前移位
                // 所以当前下标的元素进行了更新,需要在该下标再次进行讨论
                i--;
            }
        }
        return n;
    }
};

暴力解(优化)

题目对于返回数组的元素顺序没有要求。我们尝试查询所有相同元素后,批量地对身后的元素进行移动。

首先对数组进行排序,保证存在的目标元素相邻进行排列:

sort(nums.begin(), nums.end());

我们设置两个变量:

int last = -1;
int num = 0;

其中last记录末位目标元素的下标位置,num记录数组中目标元素的个数。那么之后批量对目标值的位置进行填充时,首位填充元素的下标即为last - num + 1。另外需要注意的是,能填充的元素个数与目标值个数num无关,而是由数组的边界进行限制

for (int i = last - num + 1; i + num < n; i++) {

}

算法复杂度从O(n^2)优化至O(n),完整代码如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        // 先对数组进行排序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        // last记录末位目标元素的下标
        // (这样会好处理些))
        // num记录数组中目标元素的个数
        int last = -1;
        int num = 0;
        for (int i = 0; i < n; i++) {
            if (nums[i] == val) {
                last = i;
                num++;
            }
        }

        for (int i = last - num + 1; i + num < n; i++) {
            nums[i] = nums[i + num];
        }
        return n - num;

    }
};

双指针法

图片

双指针法又被称为快慢指针法,在数组和链表的操作中十分常见。其中快指针用于遍历整个数组,而慢指针只会记录非目标值的情况:

  • 在出现目标值之前,慢指针跟快指针会同步更新
  • 出现目标值之后,慢指针跟快指针出现交错。慢指针会卡在目标值所在的下标处,当快指针再次指向非目标值元素后,就会把该值覆盖掉目标值元素。

完整代码如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size();
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < n; fastIndex++) {
            if (nums[fastIndex] != val) {
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
        }
        return slowIndex;
    }
};
posted @ 2020-12-30 16:42  脱线森林`  阅读(127)  评论(0编辑  收藏  举报