学习内容来自《代码随想录》题目建议 : 暴力的解法,可以锻炼一下我们的代码实现能力,建议先把暴力写法写一遍。 双指针法 是本题的精髓,今日需要掌握 ,至于拓展题目可以先不看。
27 移除元素(追及双指针法)
1 题目描述
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
示例 1:
输入:nums = [3,2,2,3], val = 3输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例2:
输入: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。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
2 我的思路(小白勿喷)
不清晰
3 题解
3.1 思路
移除元素,不能直接删除,只能覆盖
原地移除元素,说明不能使用额外的数组空间,也不能引入多余的元素
方法一:暴力解法
最简单的思路是,遍历数组,当找到一个val值时,就将他后面的所有元素依次全部往前移一位。
需要两层for循环,外层遍历找val值,内层往前移元素。缺点是时间复杂度高。
方法二:追及双指针法
用两个指针可以在一个for循环里实现双层for循环。
3.2 代码
方法一: 暴力解法
两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组
java版本
class Solution {
public int removeElement(int[] nums, int val) {
int length = nums.length;
//外层循环找元素
for (int i = 0; i < length; i++){
if (nums[i] == val){//如果找到目标元素,则进行移动,都向前移一位
for (int j = i + 1; j < length; j++){
nums[j - 1] = nums[j];//把j指向的内容复制到前一位上
}
i--;//移完后,i指的数值是原来位置的后一位上的值,因此i要退一位,避免出循环后,执行了i++,漏比较了当前位置的值
length--;//移完一次后,整个大小减一
}
}
return length;
}
}
python版本
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i, l = 0, len(nums)
while i < l:
if nums[i] == val: # 找到等于目标值的节点
for j in range(i+1, l): # 移除该元素,并将后面元素向前平移
nums[j - 1] = nums[j]
l -= 1
i -= 1
i += 1
return l
复杂度分析
时间复杂度:O(n^2)
空间复杂度:O(1)
方法二:追及双指针法
java版本
class Solution {
public int removeElement(int[] nums, int val) {
int slowIndex = 0;
int fastIndex = 0;
while(fastIndex <= nums.length - 1 ){
if (nums[fastIndex] != val){
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
fastIndex++;
}
return slowIndex;
}
}
python版本
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slowIndex = 0;
fastIndex = 0;
while fastIndex < len(nums):
if nums[fastIndex] != val:
nums[slowIndex] = nums[fastIndex]
slowIndex += 1
else:
fastIndex = fastIndex + 1
return slowIndex
注意这些实现方法并没有改变元素的相对位置!
复杂度分析
时间复杂度 O(n)
空间复杂度 O(1)
翻题解时看到用python内置函数来实现的,比较简单,虽然能满足功能需求,但是不能满足性能需求。
其他方法:不满足题目要求的方法
不满足题目要求的空间复杂度O(1),且原地移除的要求,remove()函数需要遍历整个列表以找到要删除的元素,并将后续的元素向前移动。这可能需要 O(n) 的时间复杂度。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
for i in nums[:]:
#这里使用了nums[:]来创建一个nums的副本,这样我们可以在遍历时对nums进行修改而不会导致问题,
#因为remove执行后会在nums上直接进行修改,会导致nums元素的下标变化,在接下来的for循环中错漏数据
if i == val:
if nums.count(i) > 0:#统计val的元素数量不为0,保证只有存在要移除的元素时才进行移除操作。
nums.remove(i)
return len(nums)
复杂度分析:
时间复杂度 O(n^2)
for i in nums[:] 遍历整个nums列表,时间复杂度为 O(n),其中 n 是列表中的元素数量。
nums.count(i) 在循环内使用count()函数来统计元素i在nums中出现的次数,其中也包含了遍历的操作。count()函数的时间复杂度为 O(n),因此总共的时间复杂度为 O(n^2)。
nums.remove(i) 在循环内使用remove()函数来移除元素i,该函数在最坏情况下的时间复杂度为 O(n),因为需要将后续元素向前移动。在平均情况下,它可能具有较低的时间复杂度。
综合起来,这段代码的时间复杂度为 O(n^2),其中 n 是列表nums中的元素数量。
空间复杂度 O(n),
nums[:] 创建了一个nums的副本,如果nums的长度为 n,那么这个副本会占用额外的 O(n) 空间。
除了这个副本外,这段代码没有占用额外的空间。
因此,这段代码的空间复杂度为 O(n),其中 n 是列表nums中的元素数量。
4 总结
4.1、使用快慢两个“指针”解决数组问题的思路
慢指针slowIndex、快指针fastIndex初始指向nums[0],
快指针fastIndex遍历原数组,
当快指针检测到某个数符合条件时,把这个数赋值给慢指针,然后快慢指针均向右移动一格,
直到快指针遍历完数组,
最后,慢指针slowIndex指向新数组末尾的下一个位置,
返回慢指针的值即为长度,返回num[0,slowIndex-1]即为新数组。
相关题目推荐
- 26.删除排序数组中的重复项
- 283.移动零
- 844.比较含退格的字符串
- 977.有序数组的平方