Problem: 27. 移除元素
工程思想
用一些python已有的工具:
while val in nums:nums.remove(val)
但是我们显然不要这么做对不对!
从零开始
看题目是要in place,则考虑交换咯。
要把不等于val的移到前面,则考虑把数组分成2部分,只要把等于val的全部交换到后面的部分就可以了。
这种就考虑用2个指针,i从前向后扫元素,把等于val的交换走,now_idx
从后向前走,记录等于val的位置。
不假思索的话,代码应该是这样:
# 这是错的!
def removeElement(self, nums: List[int], val: int) -> int:now_idx=len(nums)-1 # 从后向前扫cnt=0for i in range(len(nums)): # i从前向后扫if nums[i]==val:# 交换tmp=nums[i]nums[i]=nums[now_idx]nums[now_idx]=tmpcnt+=1 # 记录val值个数now_idx-=1 # 从后向前扫# 返回非val值的个数return len(nums)-cnt
- 等于val的数字在交换后仍然会被i再次扫到,会导致重复交换:
[val,a,b,c]
交换成[c,a,b,val]
后,i=3
的时候val还会被i找到,此时now_idx=2
,交换后导致错误。 - 在用例
[a,b,c,val,c,val]
时,i扫到val的时候,val会与后面的val交换,然后i++和now_idx–,导致交换错误。
小白改进
第一点:考虑记录一下一共要交换多少次val,用target_num
记录,交换次数到了就不再继续走了,直接退出,因为所有等于val的都已经交换过了
第二点:考虑在交换的时候直接找到数组后半部分的第一个不等于val的值,这样就不会把val交换到前面。
class Solution:def removeElement(self, nums: List[int], val: int) -> int: now_idx=len(nums)-1cnt=0target_num=0# 记录要交换多少次for num in nums:if num==val:target_num+=1for i in range(len(nums)):# 加入target num!=0,等于0的时候就不需要交换了if target_num!=0 and nums[i]==val: # 找到最前面的不是val的,并且每找到一个val,target num都要减少1,cnt要增加1while nums[now_idx]==val:now_idx-=1cnt+=1target_num-=1if target_num==0:# 此时已经结束了return len(nums)-cnttmp=nums[now_idx]nums[now_idx]=nums[i]nums[i]=tmpcnt+=1now_idx-=1target_num-=1return len(nums)-cnt
提交的最后
上述基本上能过了,但是还有特殊情况要考虑,比如空值、0等等情况。
然后把想出来的都列一下看看情况。
nums=[]
不可能,val值都没有。
nums=[1], val=1
,此时return的值应当是0,也还没问题。
提交下,过了,用时击败82%。
技巧
但再想想,是不是这样我的指针可以换一种方式进行(所谓快慢指针):
不妨考虑快慢指针,一般使用起来都是O(n),结束条件几乎都是快指针遍历结束,慢指针恰好在边界。不同点是慢指针的条件各不相同。
根据小白解法,我们让快指针去指向判定当前元素是否能进入左侧要保留部分的位置,慢指针指向保留部分的最后一个位置。也就是快指针做条件判断,慢指针做位置标识。
那么用什么条件作为快指针条件呢?
回归题意,我们希望左侧的都是不等于val,这样只要nums[fast]!=val
,就可以把fast元素放到左侧了。
class Solution:def removeElement(self, nums: List[int], val: int) -> int:slow=0cnt=0for fast in range(len(nums)):if nums[fast]!=val:# swaptmp=nums[fast]nums[fast]=nums[slow]nums[slow]=tmpslow+=1return slow
这样做不需要先统计target num,直接以快指针走到最后为结束条件;而且不需要再在交换的时候寻找位置了,因为慢指针的位置就是我想要交换的位置。
提交后也过了。但是效率不如第一版的。
小改进
由于题目说后面部分的数组的值是任意的就可以,只要返回有效数组长度就行,所以我们可以不交换,直接丢弃掉等于val值的数(换句话说,只保留前半部分的不等于val的数)。
这个简单,把上面的
tmp=nums[fast]
nums[fast]=nums[slow]
nums[slow]=tmp
# 改成
nums[slow]=nums[fast]
总结
其实都是双指针的思路,只不过一开始naive的双指针写起来比较啰嗦,而改进后的双指针写起来简洁。