动态维护有效区间:滑动窗口
右指针不断移动获取解,左指针不断移动缩小解范围
左指针的意义非常重要,相当于一个标兵,不断与这个标兵进行比较,如果符合要求,这左指针进行移动,并进行操作,如果不符合要求,则左指针不动,右指针接着寻找符合要求的值。
删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
暴力解法
一种非常简单的办法就是创建一个新的数组,每次添加元素的时候判断是否已经添加过了
class Solution:def removeDuplicates(self, nums: List[int]) -> int:res = []for v in nums:if v in res:continueres.append(v)return res
但是要求我们原地删除重复的数组,不能创建新的数组。因为数组是有序的,相同的元素已经排在了一起,如果前后两个元素不同,那么我们就找到了一个新的元素。
滑动窗口
我么定义两个指针l
和r
,右指针不断往后遍历找到与左指针不同的元素,找到以后左指针也往后走一步,然后进行交换
1. 初始化左指针和右指针
0 | 0 | 0 | 0 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
l,r |
2. 右指针不断遍历找到不同的元素
0 | 0 | 0 | 0 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
l | r |
3. 左指针移动一位,准备放入下一个不同元素
0 | 0 | 0 | 0 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
l | r |
4. 把当前不同的元素放到前面
0 | 1 | 0 | 0 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
l | r |
5. 重复之前的逻辑
右指针不断移动找下一个不同的元素
0 | 1 | 0 | 0 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
l | r |
找到后左指针移动并填充
0 | 1 | 2 | 0 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
l | r |
右指针不断移动找下一个不同的元素
0 | 1 | 0 | 0 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
l | r |
找到后左指针移动并填充
0 | 1 | 2 | 3 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|
l | r |
6.右指针到底末端结束
class Solution:def removeDuplicates(self, nums: List[int]) -> int:l = 0for r in range(len(nums)):if nums[l] != nums[r]:l += 1nums[l] = nums[r]return l+1
在这个问题中,我们可以破坏数组,所以可以直接将后面的元素覆盖到前面去
删除有序数组中的重复项 II
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
暴力解法
我们定义一个数组,不断添加元素,如果与这个数组尾端不同,则直接添加进来,如果与尾端相同,我们则判断添加的次数,超过2之后不在添加
class Solution:def removeDuplicates(self, nums: List[int]) -> int:tmp = []k = 1for i in range(len(nums)):# 如果为空则直接添加# 如果与末端元素不同也直接添加if not tmp or tmp[-1] != nums[i]:tmp.append(nums[i])k = 1# 如果与末端元素相同,则记录添加的次数k += 1# 小于2次则接着添加if k <= 2:tmp.append(nums[i])return tmp
一个小小的优化是,其实我们不需要记录重复的次数,由于末端我们只会2个相同的元素,因此只需要比较tmp[-2]
是否相同即可
temp[-3] | temp[-2] | temp[-1] | nums[i] | |
---|---|---|---|---|
前两个相同 | x | x | y | nums[i]!=tmpe[-2],说明中间只有一个y,可以直接添加 |
后两个相同 | x | y | y | 如果nums[i]==temp[-2],说明中间已经有一个y了,不能再添加了 |
全都不同 | x | z | y | nums[i]!=tmpe[-2],说明中间只有一个y,可以直接添加 |
class Solution:def removeDuplicates(self, nums: List[int]) -> int:if len(nums) <= 2:return numstmp = [nums[0],nums[1]]for i in range(2, len(nums)):# 如果为空则直接添加# 如果nums[i] != tmp[-2]说明末端元素只有一个,无脑添加if not tmp or tmp[-2] != nums[i]:tmp.append(nums[i])return tmp
滑动窗口
数组是有序的,因此所有相同的元素肯定都是在一起出现的,同时要求一个元素最多出现两次,一个简单的想法就是
右指针不断移动,直到找到与左指针不同的元素,我们要把这个不同元素保留下来,即直接放到前面去(当前左指针后面)
0 | 0 | 0 | 0 | 1 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|
l | r |
放到前面去
0 | 1 | 0 | 0 | 1 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|
l | r |
由于重复元素需要保留2个,左指针需要再移动一位存放元素
0 | 1 | 0 | 0 | 1 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|
l | r |
把重复的第2个值保留到前面
0 | 1 | 1 | 0 | 1 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|
l | r |
当前这个元素已经满足要求了(不超过2个),右指针接着往后找下个不同的元素
0 | 1 | 1 | 0 | 1 | 1 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|
l | r |
我们需要记录当前元素已经找到几个相同的了,如果小于等于2,那么左指针都需要跟着走,如果大于2了,左指针就不要动了,一直等找到下一个不同的元素
理清思路
好好理一下思路
- 右指针不断后移找到不同的元素
- 定义一个变量,记录当前的元素重复的次数
- 如果小于等于k,那么左指针移动,并将这个元素保留过来
- 如果重复次数超过了k,左指针不动,一直找到下一个重复元素,k重新赋值为1
class Solution:def removeDuplicates(self, nums: List[int]) -> int:if not nums: # 处理空数组return 0l = 0k = 1 # nums[l] 初始出现1次# r从1开始,因为0位置已被l标记for r in range(1, len(nums)):if nums[l] == nums[r]:k += 1# 出现次数<=2时,扩展有效数组if k <= 2:l += 1nums[l] = nums[r] # 这里漏了赋值,需要补充else:# 遇到新元素,重置计数并扩展有效数组k = 1l += 1nums[l] = nums[r]return l + 1
滑动窗口优化
因为数组是有序的,重复元素会连续排列。假设有效数组的最后两个元素是nums[l-2]和nums[l-1](当l >= 2时),此时:
- 如果num == nums[l-2],说明nums[l-2]、nums[l-1]、num三者相等(因为数组有序,中间的nums[l-1]必然也等于num),加入num就会导致该元素出现 3 次,不符合要求。
- 如果num != nums[l-2],说明即使num和nums[l-1]相等(最多出现 2 次),也不会超过限制,因此可以加入有效数组。
from typing import Listclass Solution:def removeDuplicates(self, nums: List[int]) -> int:l = 0 # 慢指针:指向有效数组的末尾for num in nums:# 核心判断:# 1. 有效数组长度不足2时,直接加入(前两个元素一定有效)# 2. 超过2个元素时,若当前元素与有效数组倒数第二个不同,说明可加入(避免3次重复)if l < 2 or num != nums[l-2]:nums[l] = numl += 1return l