leetcode刷题记录(二)——27.移除元素

(一)问题描述

leetcode问题地址icon-default.png?t=O83Ahttps://leetcode.cn/problems/remove-element/

       给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

       假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

示例1:

输入:nums=[3,2,2,3], val=3

输出:2,nums=[2,2,_,_]

解释:你的函数应该返回k=2,并且nums中的前两个元素均为2。你在返回的k个元素之外留下了什么并不重要(因此它们并不计入评测)。

示例2:

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

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

解释:你的函数应该返回k=5,并且原数组nums的前5个元素被修改为0,1,4,0,3。你在返回的k个元素之外留下了什么并不重要(因此它们并不计入评测)。

提示:

  • 0<=nums.length<=100
  • 0<=nums[i]<=50
  • 0<=val<=10

(二)关键词提取

  1. 移除数组元素
  2. k个元素后不参与评测

       考验对数组底层的实现的理解:数组的地址空间是连续的,删除一个元素,其他元素的地址也会移动。所以数组的元素是不能被删除的,只能被覆盖。因此即使移除了一个元素,实际数组占用的物理空间是不变的。只不过C++、Java、Python等编程语言会对数组做一个包装,有一个计数器,删除元素之后返回的数组长度会减1。

(三)解题思路

1. 双指针法(快慢指针)

       定义快(fast)、慢(slow)两个指针。快指针的作用是找到新数组需要的元素。找到新数组需要的元素,需要赋值给新数组(或者说将新数组的元素值赋值到合适的位置),慢指针就指向新数组的元素应该被赋值的位置。

  • 赋值发生的条件:快指针指向的值不等于val。不满足这个条件时,快指针将继续向前移动,直到满足赋值发生的条件。赋值结束后,快指针将继续移动。
  • 赋值的位置:慢指针所指向的位置(慢指针就是新数组的下标)。经历一次赋值之后,慢指针的位置才会向前移动。如果快指针当前指向的位置不符合赋值发生的条件(即赋值没发生),那么慢指针将原地等待,直到快指针指向的值不等于val(赋值发生)。

       快指针一定会先于慢指针遍历完数组,即先找到值,后找到位置。有k个新元素的值,就一定赋值了k次。所以不需要判断慢指每次指向的值是否等于val,所有新数组需要的元素一定都在移动完成时被赋值到合适的位置。

伪代码:

RemoveElement (nums[0,...,n],val)

//快慢指针法实现数组元素原地移除

//输入:数组nums,需要移除的值val

//输出:删除元素后的数组长度k

slow←0, fast←0

for fast←0; fast<nums.length; fast++

     if nums[fast] != val

         nums[slow]←nums[fast]

         slow++

return slow

2. 相向双指针法

        与快慢指针法类似,定义左(left)、右(right)两个指针,分别从数组的开端和结尾开始相向移动。右指针的作用是找到新数组需要的元素。找到新数组需要的元素,需要赋值给新数组(或者说将新数组的元素值赋值到合适的位置),左指针就指向新数组的元素应该被赋值的位置。

  • 赋值发生的条件:左指针的值等于val,且右指针的值不等于val。如果不满足前者,说明当前位置不需要移除,左继续向前移动;如果不满足后者,当前右指针指向的元素不能用来赋值,右指针将继续向前移动直到找到新数组的值。
  • 赋值发生的位置:左指针等于val时指向的位置。无论是否发生赋值,左指针都会向前移动。

伪代码:

RemoveElement (nums[0,...,n],val)

//相向双指针法实现数组元素原地移除

//输入:数组nums,需要移除的值val

//输出:删除元素后的数组长度k

left←0, right←nums.length-1

while right>=0 and nums[right]==val 

         right←right-1

while left<=right   

    if nums[left]==val

         nums[left]=nums[right]

         right←right-1

    while right>=0 and nums[right]==val 

         right←right-1

    left←left+1

return left 

(四)易错点

         1. 库函数的使用。C++、Java、Python等语言中都有删除数组元素的函数。这个问题不适合直接使用这些函数。如果只使用库函数就能解决这个问题,那这个问题最好不用库函数。

         2. k个元素之后的值如何处理。我一开始的做法是将值等于val的元素与不等于val的元素进行交换,即新数组与原数组的元素完全一致,前k个是不等于val的元素,k个之后的全是等于val的元素。其实没有必要移动元素,只要对前k个元素进行赋值就可以了

        3.相向双指针法循环的执行条件和返回值的选择有两种情况:(1)left<right,循环结束后返回left+1; (2)left<=right,循环结束后返回left 。

        我第一遍写的是(1),然后出错了。出现错误的用例是[1]。显然(1)会在nums中所有元素都等于val时出错。原因在于方式(1)相当于在统计最后结果时,要包括left所指向的元素;而方式(2)在返回最终结果时是不包括最后left所指向的元素的。当所有元素都等于val时不应当保留任何一个元素,所以(1)才会出错。

       下次在写的时候最好单独考虑一下极端情况,这样在确定条件的时候不容易出错。

     4. 相向双指针法当中,每次循环要通过while循环让right指向前进方向距离当前位置最近的非val元素。这个操作应该放在if判断之前还是之后。我第一遍的写法是放在之前,我的想法是,在判断和值覆盖开始之前,right的初始位置就应该在第一个非val元素上了,所以应该放在if之前。

       出现问题还是在数组中所有元素都等于val时。如果按我第一遍的写法,大循环是执行的,在小循环里right直接移到-1,下面if判断完了又要交换值,会直接报错数组出界。正确的写法是在大循环外先将right调整到合适位置。从第一轮循环开始,每次都在判断完之后再调整right位置。这样下一轮循环开始时,left+1时right已经在合适的位置上了,也不会出现left先加+1再调整right,right已经小于left了循环还要执行一下的情况。

       5.一个其他题解里都没写,但是我觉得也应该添加进来的小细节,就是给的那个提示,对数组长度、数组元素和val的取值范围都有规定,那应该提前判断一下,避免不必要的操作。

if(nums.length==0){return 0;}
if(val>50){return nums.length;}
int left=0, right=nums.length-1;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值