大家好呀,我是帅蛋。
最开始在讲数组的时候并没有写实战题,当时想的是数组这么简单,有啥好写的。
直到最近有蛋粉同我讲,才发现是我自己 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; } }
图解移除元素到这就结束辣,数组实战第一篇文章,是不是很有感觉?
这只是一道开胃菜,精彩的题目还在后面呐。
大家记得帮我点赞+在看呀,让我麻溜肝起来!
我是帅蛋,我们下次见!