代码随想录刷题-数组-移除元素

移除元素

本节对应代码随想录中:代码随想录,对应视频链接为:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili

习题

题目链接: 27. 移除元素- 力扣(LeetCode)

给你一个数组 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。

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

我的想法

非较优答案,仅为个人思路记录

我的想法是 for 循环从头遍历 nums,并用 res 记录最终结果初始为0。如果当前遍历到的值等于 val,则让 res 位置和遍历到的 val 所在位置替换下,这样一轮 for 循环后前 res 位置都是 val。

新的长度用总长度减去 res 即可。而由于题目要求 nums 前 res 个要返回非 val 的数值,因此还要一轮 for 循环将前 res 个元素和后 res 个元素替换。

class Solution {
   public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size(), res = 0, temp;
        for (int i = 0; i < n; i++) {
            if (nums[i] == val) {
                // 如果和val相等则和让当前值和前面的值替换
                temp = nums[i];
                nums[i] = nums[res];
                nums[res] = temp;
                res++;
            }
        }
        // 把和val相等的几个值放到后面
        for (int i = 0; i < res; i++) {
            temp = nums[i];
            nums[i] = nums[n - i - 1];
            nums[n - i - 1] = temp;
        }
        return n - res;
    }
};
  • 时间复杂度:O(n)。这段代码只对数组进行了一次遍历,所以时间复杂度为 O(n)。
  • 空间复杂度:O(1)。这段代码只使用了常数级别的额外空间,即定义了几个整型变量,因此空间复杂度为 O(1)。

后来看了题解后,想了下发现前 res 个可以直接删除而不用和后面 res 个替换

nums.erase(nums.begin(), nums.begin() + res);

暴力解法

两层 for 循环,第一层循环从头遍历数组,如果遇到和 val 相等的值,则用第二层 for 循环将当前位置之后的元素都像前移动一位。

例如 0 1 2 3 2 5,val=2

  • 第一层 for 循环从左到右遍历,遇到 nums[2]=2时,将后面的 3 2 5向前移一位变成0 1 3 2 5
  • 接着继续遍历(3 2 5),遇到第二个2时,将后面的5向前移一位变成0 1 3 5

题目中说了不用考虑超过新长度范围外的元素

代码如下:

需要注意的是向前移动一位后,i 要减1,如上面的例子,nums[2]=2,移动后变成0 1 3 2 5,此时若不减1下一次 i=3,将会跳过3与 val 的比较而是比较第二个2与 val。同时 size 减一方便记录新数组的长度。

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];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};
  • 时间复杂度:O(n^2)。这段代码使用了两层循环。外层循环遍历整个数组,内层循环将需要移除的元素之后的元素向前移动一位。因此,总的时间复杂度为 O(n^2)。
  • 空间复杂度:O(1)。这段代码只使用了常数级别的额外空间,即定义了几个整型变量,因此空间复杂度为 O(1)。

与我自己想的解法相比,这个解法在找到 val 时将后面的元素向前移动一位,从而保证前面的元素都是题目要求的。

而我的解法是将找到的 val 放到前面,之后再把他们放到后面(直接放到后面会覆盖后面的元素)。

双指针

双指针:通过一个快指针和慢指针在一个for循环下完成两个for循环的工作

快指针从头遍历元素指向当前将要处理的元素,慢指针指向下一个将要赋值的位置。

  • 如果快指针指向的元素不等于 val,它一定是输出数组的一个元素,我们就将快指针指向的元素复制到慢指针位置,然后将两个指针同时右移;
  • 如果快指针指向的元素等于 val,它不能在输出数组里,此时慢指针不动,快指针右移一位。

这样左边的元素都不等于 val

与前面的两个解法相比,用慢指针替代了前面的第二个 for 循环。关键语句是 nums[slowIndex++] = nums[fastIndex]; 将后面的值直接复制到前面合适的位置。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (nums[fastIndex] != val) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};
  • 时间复杂度:O(n)。由于这段代码只对数组进行了一次遍历,所以时间复杂度为 O(n)。
  • 空间复杂度:O(1)。这段代码只使用了常数级别的额外空间,即定义了几个整型变量,因此空间复杂度为 O(1)。

这里的快指针相当于常写的 for (int i = 0; i < size; i++) 中的 i 一样,遇到和 val 相等的值时,再用一个慢指针将和 val 相等的值移到前面。如果将 fastIndex 改成常写的 i 就会发现其实就是用 nums[slowIndex++] = nums[fastIndex]; 解决了上面两种解法好几行才能解决的问题。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] != val) {
                nums[slowIndex++] = nums[i];
            }
        }
        return slowIndex;
    }
};

双指针优化

当移除的元素在开头时,我们要将每个元素都左移一位。如[1,2,3,4,5],val=1。实际上只要将右边的5移到左边的1的位置即可。

可以使用两个指针,分别位于数组的左右两端,如果左指针等于 val 则将右指针指向的元素复制到左指针的位置。直到两个指针重合为止。

需要注意的是如果写成 right = nums.size()-1 那 while 的条件是 <=。原因是最后返回的是 left 值,当 left=right 时,如果此时 nums[left] != val 还是要将 left+1。如 nums={3,2,2,3},val=3 时,当 left=right=1,nums[1]=2时,还是要执行一次 while 让 left+1,从而返回 left=2,如果 while 写的 < 那就会错误的返回 left=1

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int left = 0, right = nums.size()-1;
        // 注意是<=
        while (left <= right) {
            if (nums[left] == val) {
                nums[left] = nums[right];
                right--;
            } else {
                left++;
            }
        }
        return left;
    }
};
  • 时间复杂度:O(n)。其中n是数组nums的长度,因为我们最多只遍历整个数组一次
  • 空间复杂度:O(1)。因为我们只使用了常量级别的额外空间来存储left和right两个指针

总结:

  • 我的想法遇到和 val 相等的元素先替换到前面再替换到后面
  • 暴力解法遇到 val 相等的元素时,将后面的元素都向前移一位
  • 双指针遇到 val 相等的元素时,用慢指针将后面的非 val 元素替换到前面的合适位置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值