- 给你一个数组 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。
力扣题目链接:27 移除元素https://leetcode.cn/problems/remove-element/
1. 暴力解法:直接两个循环
第一个循环:找到目标值i
第二个循环:更新数组,从第i个值开始,a[i]=a[i+1](考虑 或是否要用“修改”一词,但最终采用更新,原因是数组无法被修改,只能被覆盖)数据基本理论
需要注意两个点:
1. for j in range(i, length - 1) : nums[j] = nums[j + 1]处
- 最开始写的 range(i, length),是错的
- 原因是, 当j = length - 1时,nums[j + 1]没有意义
2. i -= 1 最开始没写
- ## 这个尤其要注意,很容易忽略 ##
- 写它是因为删除一个数后,由于下标 i 以后的数值都向前移动了一位,所以i也向前移动一位,使得while循环中进行 i + 1操作后,得到的nums[i],是原本没删除时应该要搜索的后一位数
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i = 0
length = len(nums)
while i < length:
if nums[i] == val:
# 最开始写的 range(i, length),是错的
# 原因是, 当j = length - 1时,nums[j + 1]没有意义
for j in range(i, length - 1):
nums[j] = nums[j + 1]
length -= 1 # 因为删掉了一个数,所以数组长度要-1
## 这个尤其要注意,很容易忽略 ##
i -= 1 # // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
i += 1
return length
2. 快慢双指针法:
快慢指针法: 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作
定义快慢指针
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
算法逻辑:
- 当未寻找到目标值时,即nums[fast] != val, nums[slow] == nums[fast],两个指针齐头并进,
- 一旦找到目标值,slow指针不前进,fast指针前进
- 这样fast遇到下一个非目标值a时,就可使得nums[slow] = a,直接覆盖掉那个目标值,从此以后快慢指针间隔一个位数同时前进
- 因此快慢指针间隔的位数就是覆盖掉的目标值个数,而慢指针的位置就是剩余元素的个数
注意两个点:
1. while fast < size, 不加等于是因为,fast = size 时,nums[fast ] 会越界
2. 返回slow不是slow + 1,是因为它指向的是最后一个非要移除元素之后的位置
我当时考虑到slow + 1是因为数组的下标从0开始,数组的个数 = 最后一个数的下标 + 1
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
fast = 0
slow = 0
while fast < len(nums):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
3. 相向双指针法
相向双指针法: 通过一个头指针和尾指针在一个for循环下完成两个for循环的工作
定义头尾指针
- 头指针:寻找新数组的元素 ,同时替换目标元素
- 尾指针:寻找可以被替换的数据
算法逻辑:
- 当头尾指针未相碰时,执行外层大循环
- 第一层内部循环:头指针遇到目标对象则跳出循环,等待执行if语句
- 第二层内部循环:尾指针如果遇到不是目标值的对象,则跳出循环,等待下面if语句执行;如果遇到目标对象的值,则向左移动,这样就可以覆盖掉尾部的目标值,知道遇到下一个非目标值
- if语句就是将尾指针遇到的非目标值赋给头指针遇到的目标值,从而替换
需要注意的点:
1. 还是 <、<= 的问题
这里到底怎么用其实跟二分法大差不差,具体可以看这一篇:二分法
这里用小于等于,是因为right的初始值为 n-1,left <= right有意义
2. 最后跳出大循环时,由于if语句,left += 1,会指向剩余所有元素的后一位,因此return left即可,不需要left + 1
3. 两个内层循环中的 left <= right 条件是必要的,如果没有,当处理完目标值后,left不会跳出循环而会一直增加,从而跳出数组边界而后报错
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
left = 0
right = n - 1
while left <= right:
while left <= right and nums[left] != val:
left += 1
while left <= right and nums[right] == val:
right -= 1
if left < right:
nums[left] = nums[right]
left += 1
right -= 1
return left