————技巧点:双指针法
1、移动零(283)
题目描述:
【简单题】
思路分析
要求:移动零到数组的末尾
约束:在原数组上操作,不能拷贝额外的数组
题解一:双指针法
-
首先,遍历整个数组,在遍历的过程中,如果碰到非0的元素,那么就将该元素 向前移动。此时,可设置一个临时指针
k
(初始为0),碰到非零元素,则将令nums[k]=nums[i]
,同时k++
。 -
当遍历完成后,将最后一个非零元素(nums中k的位置) 到 数组结尾 ,这一部分的位置,全部置为0。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
k=0
for i in range(len(nums)):
if nums[i]:#如果nums[i]不为零
nums[k]=nums[i]
k+=1
# 非0元素统计完了,剩下的都是0了
# 所以第二次遍历把末尾的元素都赋为0即可
for j in range(k,len(nums)):
nums[j]=0
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
题解二:双指针法
\quad \quad 对题解一进行一个优化,将非零元素与零元素进行交换。
-
设置一个临时指针即慢指针k(初始为0)。
-
遍历数组,其中for循环中的
i
就相当于一个当前指针。- 遇到非零元素nums[i],就交换当前指针和慢速指针指向的元素即将
nums[i]
与nums[k]
交换,然前进两个指针即k++,i随着循环就前进了。【考虑一个特殊情况:如果首个元素非零时,此时i=k,没有交换的必要,因此只需要将k++。故更优化的情况下,在这个条件下再加一个约束】 - 如果它是零元素,我们只前进当前指针,k不变
- 遇到非零元素nums[i],就交换当前指针和慢速指针指向的元素即将
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
k=0
for i in range(len(nums)):
if nums[i]:
if i!=k:#如果i=k,就没有交换的必要了,所以加了这个约束
nums[k],nums[i]=nums[i],nums[k]
k+=1
else:
k+=1
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
2、移除元素(27)
题目描述:
【简单题】
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
思路分析
双指针法
-
设置一个临时指针即慢指针k(初始为0)
-
遍历数组,其中for循环中的
i
就相当于一个当前指针或快指针。- 当
nums[i]
与给定的值相等时,递增i(因为在循环中,i本身就在递增,所以不用做任何事情) 以跳过该元素。 - 只要
nums[i] != val
,就复制nums[i]
到 nums[k] 并同时递增两个指针(k++,i不用做什么)。重复这一过程,直到i到达数组的末尾,该数组的新长度为k。
- 当
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
k=0
for i in range(len(nums)):
if nums[i]!=val:
nums[k]=nums[i]
k+=1
return k
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
3、删除排序数组中的重复项(26)
题目描述:
【简单题】
给定一个排序数组,你需要在 原地
删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地
修改输入数组 并在使用 O(1) 额外空间的条件下完成。
题目链接
思路分析
要求:保留排序数组中每个元素只出现一次
约束:在原数组上操作,不能拷贝额外的数组
\quad \quad 考虑到数组是排序的,依次遍历数组并跳过重复项的话,当前元素只能跟前面的有重复的可能性。此时,我们就可考虑用双指针法。
双指针法
-
设置一个临时指针即慢指针
j
(初始为0),辅助判断是否为重复元素。 -
遍历数组(从第一个元素开始),其中for循环中的
i
就相当于一个当前指针或快指针。- 当
nums[i]=nums[j]
时,递增i(因为在循环中,i本身就在递增,所以不用做任何事情) 以跳过该元素。 - 只要
nums[i] !=nums[j]
,就复制nums[i]
到nums[j]
并同时递增两个指针(j++,i不用做什么)。重复这一过程,直到i到达数组的末尾,该数组的新长度为j+1。
- 当
【python代码实现】
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
j=0
for i in range(1,len(nums)):
if nums[i]!=nums[j]:
j+=1
nums[j]=nums[i]
return j+1 #因为遍历数组且第一位置开始的,默认nums[0]是存在的,故返回j+1
-
时间复杂度: O ( n ) O(n) O(n),假设数组的长度是 n,那么i 和 j 分别最多遍历 n 步。
-
空间复杂度: O ( 1 ) O(1) O(1)。
4、删除排序数组中的重复项II (80)
题目描述:
【中等题】
给定一个排序数组,你需要在原地
删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地
修改输入数组并在使用 O(1) 额外空间的条件下完成。
题目链接
思路分析
要求:保留排序数组中每个元素最多出现两次,返回移除后数组的新长度。
约束:在原数组上操作,不能拷贝额外的数组
\quad \quad 与leetcode26题唯一不同的是一个元素重复的情况下可以出现两次,所以我们可以在26题的基础上再添加一个count计数指针,统计每个元素出现的次数。
三指针法
-
设置三个指针,
i
是遍历指针,指向当前遍历的元素;j
指向下一个要复制元素的位置(初始为1,因第一个元素肯定是存在的)。count
指针 记录当前数字出现的次数。count 的最小计数始终为 1。 -
遍历数组(从索引 1 开始一次处理一个数组元素)。
- 若当前元素与前一个元素相同,即
nums[i]==nums[i-1]
,则 count++。否则,count=1 - 若 count > 2,则说明遇到了多余的重复项。在这种情况下,我们只向前移动 i(遍历过程中就在移动),而 j 不动。此时,什么都不许做。
- 若 count <=2,则我们将 i 所指向的元素移动到 j 位置即复制
nums[j]=nums[i]
,并同时增加 i 和 j。
- 若当前元素与前一个元素相同,即
-
当数组遍历完成,则返回 j。
【python代码实现】
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
j,count=1,1
for i in range(1,len(nums)):
if nums[i]==nums[i-1]:
count+=1
else:
count=1
if count<=2:
nums[j]=nums[i]
j+=1
return j
方法二:
具体思路见下面那一道题
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
def solve(k):
u = 0
for x in nums:
if u < k or nums[u - k] != x:
nums[u] = x
u += 1
return u
return solve(2)
5、删除排序数组中的重复项III (保留K位)
题目描述:
【中等题】
\quad \quad
此为前两题题的进阶版,将原问题的排序数组「保留 2 位」或「保留 1 位」 修改为「保留 k 位」。
对于此类问题,有以下思路:
- 由于是保留 k 个相同数字,对于前 k 个数字,我们可以直接保留
- 对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第 k 个元素进行比较,不相同则保留
def removeDuplicates(nums,k):
j=0
for x in nums:
if j<k or nums[j-k] !=x:
nums[j]=x
j+=1
return j,nums[0:j]