zl程序教程

您现在的位置是:首页 >  其他

当前栏目

ACM 选手图解 LeetCode 移除元素

2023-04-18 17:01:24 时间

大家好呀,我是帅蛋。

最开始在讲数组的时候并没有写实战题,当时想的是数组这么简单,有啥好写的。

直到最近有蛋粉同我讲,才发现是我自己 xue xue 有些飘了。

能写的明明很多嘛!

这让我想到了“知识的诅咒”,当一个人知道了某事,就无法想象这件事在未知者眼中的样子

我引以为戒。


因为数组实在太常用了,我就挑几种类型的题来讲一下,大致是图中读者说的那几个。

以后想起来别的再继续补充。

今天解决移除元素,是快慢指针的经典题目。话不多说,直接开整。

LeetCode 27:移除元素

题意

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

示例

输入:nums = [0,1,2,2,3,0,4,2], val = 2

输出:5, nums = [0,1,4,0,3]

解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示

必须使用 O(1) 空间复杂度并原地修改输入数组。

  • 0 <= len(nums) <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

题目解析

水题,题目简单。

这道题相当于数组元素的删除,既然涉及到数组的删除,就不是想当然的删除。

我在文章【ACM 选手带你玩转数组】中讲过:

数组是用连续的一段内存空间,来存储相同数据类型数据的集合。因为这个“连续内存存储”,使得数组在插入或者删除的元素的时候,其它元素也要相应的移动。

这道题数组的数据量很小,最多只到 100 个,所以可以用暴力手法破解。

所谓暴力手法就是最无脑的方式,两层 for 循环,第一层 for 循环从 0 开始遍历数组,第二层 for 循环维护后面的元素向前移动。

比如第一层 for 循环遍历到下标为 2 的位置发现 nums[2] == val,那么第二层循环就将 i+1 ~ n -1 位置上的元素全部向前移动一个位置。

因为是双重 for 循环,此时的时间复杂度为 O(n²)。

虽然也能 AC,但显然作为新时代的三好男人,本帅蛋在这方面追求的肯定不是慢,而是秒男的极致,我要快!

要我快点的话,这就不得不提快慢指针了。

快慢指针,顾名思义,是使用速度不同的指针(可用在链表、数组、序列等上面),来解决一些问题。

快慢指针用 fast 和 slow 两个指针控制,快指针 fast 指向当前要和 val 对比的元素,慢指针 slow 指向将被赋值的位置

  • 遍历数组。
  • 如果 fast 指针指向的元素 nums[fast] != val,则 nums[fast] 是输出数组的元素,将其赋值到 nums[slow] 的位置,slow 和 fast 同时向后移动一位。
  • 如果 nums[fast] == val,证明当前 nums[fast] 是要删除的元素,fast 向后移动一位。
  • fast 遍历完整个数组,结束,slow 的值就是剩余数组的长度。

图解

以 nums = [0,1,2,2,3,0,4,2], val = 2 为例。

首先初始化 fast 和 slow 指针。

# 初始化快慢指针
fast = slow = 0

遍历整个数组。

第 1 步,fast = 0,slow = 0,此时 nums[fast] = 0,不等于 val。

此时 nums[slow] = nums[fast](虽然它俩现在本来就是一个),slow 和 fast 向后移动一位。

# 如果快指针指向的值不等于 val
# fast 指向位置的元素向 slow 指向的位置赋值
if nums[fast] != val:
    nums[slow] = nums[fast]
    slow += 1
    fast += 1

第 2 步,fast = 1,slow = 1,此时 nums[fast] = 1,不等于 val。

此时nums[slow] = nums[fast],slow 和 fast 向后移动一位。

第 3 步,fast = 2,slow = 2,此时 nums[fast] = 2,等于 val。

此时 fast 向后移动一位,slow 不动。

# 如果快指针指向的值等于 val
# 只 fast 指针移动,不向 slow 指针指向的位置赋值
fast += 1

第 4 步,fast = 3,slow = 2,此时 nums[fast] 依然是 2,等于 val。

所以还是 fast 向后移动一位,slow 不动。

第 5 步,fast = 4,slow = 2,此时 nums[fast] = 3,不等于 val。

此时nums[slow] = nums[fast] = 3,slow 和 fast 向后移动一位。

第 6 步,fast = 5,slow = 3,nums[fast] = 0,不等于 val。

此时nums[slow] = nums[fast] = 0,slow 和 fast 向后移动一位。

第 7 步,fast = 6,slow = 4,nums[fast] = 4,不等于 val。

此时nums[slow] = nums[fast] = 4,slow 和 fast 向后移动一位。

第 8 步,fast = 7,slow = 5,nums[fast] = 2,和 val 相等。

此时 fast 向后移动一位,slow 不变,数组遍历结束。

可以看出,此时从 [0, slow) 为剩下的输出数组,slow 的值为剩下数组的长度。

本道题的解法,使用了一层 for 循环,所以时间复杂度为 O(n)

额外使用了两个指针 fast 和 slow,空间复杂度为 O(1)

代码实现

Python 代码实现

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
​
        if len(nums) == 0:
            return 0
        # 初始化快慢指针
        fast = slow = 0
​
        while fast < len(nums):
            # 如果快指针指向的值不等于 val
            # fast 指向位置的元素向 slow 指向的位置赋值
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            
            # 如果快指针指向的值等于 val
            # 只 fast 指针移动,不向 slow 指针指向的位置赋值
            fast += 1
​
        return slow

Java 代码实现

class Solution {
    public int removeElement(int[] nums, int val) {
        // 初始化快慢指针
        int fast = 0;
        int slow = 0;
        for (; fast < nums.length; fast++) {
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
​
    }
}

图解移除元素到这就结束辣,数组实战第一篇文章,是不是很有感觉?

这只是一道开胃菜,精彩的题目还在后面呐。

大家记得帮我点赞呀,让我麻溜肝起来!

我是帅蛋,我们下次见!