代码随想录算法训练营打卡day01-2|27 移除元素(快慢双指针法)

本文介绍了LeetCode第27题的解决方案,主要探讨了两种方法:暴力解法和追及双指针法。暴力解法通过两层循环实现,时间复杂度较高;双指针法则更优,能在O(n)时间内完成,且原地修改数组。文章提供了Java和Python的代码示例,并强调了双指针法的重要性。
摘要由CSDN通过智能技术生成
学习内容来自《代码随想录》
题目建议 :  暴力的解法,可以锻炼一下我们的代码实现能力,建议先把暴力写法写一遍。  双指针法 是本题的精髓,今日需要掌握 ,至于拓展题目可以先不看。 

 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.有序数组的平方
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值