LeetCode-移除元素、删除有序数组中的重复项
务必清晰理解记忆每个变量、每个函数的定义,这样做题才不会迷茫。题解中定义的各个变量的含义,这是理解的基础。
移除元素
- 题目:
给你一个数组 nums 和一个值 val,你需要 原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
数组
- 在数组中是如何移除元素的?
首先要知道数组 在内存中的 存储方式 :存放在连续内存空间上的相同类型数据的集合。
通过下标索引获取数据。
1.1 数组下标从0开始
1.2 数组内存空间是连续的
移除(或添加)元素的时候就要,进行数据的移动覆盖
数组中的元素不能删,只能覆盖
暴力for循环
循环找到目标元素,后面元素向前移动覆盖
def removeElement(self, nums, val):
i = 0
l = 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
■ 算法选择分析: 当需要 遍历数组或者字符串并从中找出满足条件的子集或者子串的时候,我们最容易想到的也是最简单暴力的方法就是使用嵌套的循环来实现,但是通常这样子的程序时间复杂度会太高甚至导致程序运行超出时间限制,此时我可以考虑尝试使用双指针法来对算法进行优化。
双指针法
顾名思义,就是需要定于两个“指针”(不同于C语言中的指针)。我们最常用的就是对撞指针(相反方向)和快慢指针(相同方向) 两种方法。
对撞指针,即两个指针从两侧开始向中间遍历有序数组,将这两个指针分别定义为左指针left和右指针right
,我们常用的二分查找也是利用了这钟算法思想,常见的还有两数之和、反转数组等问题的求解;
快慢指针,即两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针fast和慢指针slow
,两个指针以不同的策略移动来解决问题,常用于查找或者删除相同元素、判定链表中是否含有环、寻找链表的中点等相关问题的求解。在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法
还有一个分离双指针:两个指针分别属于不同的数组 / 链表。适合解决有序数组合并,求交集、并集问题。
- 思路
- 原地 移除数组中所有数值等于 val 的元素,输出数组的长度 肯定等于或小于 输入数组长度,可以把输出数组直接写在输入数组上,进行赋值覆盖移动。就不用使用额外的空间了
- 双指针:快指针指向当前要处理进行比较的元素,慢指针指向下一个将要被赋值的位置。
- 快指针指向的元素等于 val,则进行元素移除,他不能出现在输出数组中,慢指针不动,快指针继续向下遍历
- 快指针指向的元素不等于 val,他一定时输出数组中的一个元素,则将快指针指向的元素值复制给慢指针位置,进行覆盖。快慢指针同时右移
- 整个过程中唯一不变得性质:区间[0, slow)中的元素都不等于val。当快慢指针遍历完之后,慢指针的值就是输出数组的长度。
双指针(快慢指针)
# 使用for 循环和range()函数
def removeElement(nums, val):
n = len(nums)
slow = 0
for fast in range(0, n):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
# 用while循环
def removeElement(nums, val):
n = len(nums)
slow, fast = 0, 0
while fast < n: # fast指针遍历整个数组 每一次循环遍历fast都向后移动一位
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
在运行结束的时候,我发现使用range()的代码占用内存比较大。查找资料发现,range()函数的时候,会根据参数生成一个指定步长的数组序列,相应的也会占用一定的内存空间。
range参数过大时会占用大量内存
- 复杂度
- 时间复杂度:O(n):n为数组的长度,我们只需要遍历该序列至多两次
空间复杂度:O(1):我们只需要常数的空间保存若干变量。
双指针优化(对撞指针)
- 思路
思路分析(注意要点)
-
正常情况下移除数组中的一个元素,如果元素在数组的开头,e.g.序列[1, 2, 3, 4, 5],移除元素1,我们需要将1后面的所有元素都左移一位。
题目中说元素的顺序可以改变,可以直接将最后一个元素5移动到序列开头, 得到序列[5, 2, 3, 4],同样满足题意。(适用于 序列中val元素的数量较少 的情况) -
对撞指针:两个指针分别位于数组的首尾,向中间移动遍历该序列。
-
左指针left
就是直接指向需要被移除的元素val, 只要满足条件nums[left] == val
, 直接用right右指针
指向的元素将其替换,直到左指针指向的元素不等于val位置 -
当左右指针重合的时候,左右指针遍历完了数组中的所有元素。
-
两个指针在最坏的情况下只遍历了数组一次,同时避免了需要保留的元素重复赋值操作
-
右指针 的初始值的选取(数组长度/数组长度-1), 不同的选取值决定了while的不同循环条件,
class Solution:
def removeElement(self, nums, val):
left, right = 0, len(nums)
while left < right:
if nums[left] == val:
nums[left] = nums[right-1]
right -= 1
left += 1
return left
# 右指针为数组下标时
def removeElem(self, nums, val):
left = 0
right = len(nums)-1
while left <= right:
if nums[left] == val:
nums[left] = nums[right]
right -= 1
# nums[left] != val 左指针向右移动一位,右指针不不动
else:
left += 1
return left
【快慢指针】26. 删除有序数组中的重复项
-
题目描述:
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
-
思路:
有序数组 nums ,原地 删除重复出现的元素,不要使用额外的数组空间。
则需要我们一边遍历,一遍查找相同元素进行对比,发现不同的元素时,对原数组进行修改。
- 有序数组:相等的元素在数组中的下标一定是连续的对于任意的 i<j,如果nums[i]=nums[j],则对任意的i<=k<=j,必有nums[i]=nums[k]=nums[j]。利用有序数组的特点,可以通过双指针的方法删除重复元素。
- 数组nums的长度为0时,数组不包含任何元素,因此返回0 。
- 双指针(快慢指针)
初始化指针slow
指向数组开始的位置nums[0]
, 表示当前不同元素的下标位置,
fast指针
表示遍历数组达到的下标位置
- 当数组nums的长度大于0时,数组中至少包含一个元素,在删除重复元素之后也至少剩下一个元素,从下标为1的开始删除重复元素。
fast
指针指向nums[1]
,从下标为1的位置遍历整个数组。
fast指针
不断向后遍历,并将指向fast指针
的元素向slow指针
指向的元素进行比较。
· 当nums[fast] != nums[slow]
时(nums[fast] != nums[fast-1]
),说明快指针和之前的元素都不相同,slow的值加1,即slow指针
指向下一个位置。快指针的指向的元素值赋值给慢指针,nums[slow+1] = nums[fast]
· 当nums[fast] == nums[slow]
时,fast指针
继续向后遍历
遍历结束后,从nums[0] 到nums[slow]的每个元素都不相同,且包含原数组中每一个不同的元素,因此新的长度为slow+1, 返回slow+1
- python代码
def removeDuplicates(nums):
if not nums:
return 0
slow, fast = 0, 1
while fast < len(nums):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
fast += 1
return slow + 1
- 复杂度
时间复杂度:O(n):n 为数组长度,我们最多遍历该数组一次
空间复杂度:O(1):我们只需要常数的空间存储若干变量。
- 题目
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
class Solution:
def removeDuplicate(slef, nums):
slow = 0
for fast in range(len(nums):
if slow < 2 or nums[fast] != nums[slow -2]:
nums[slow] = nums[fast]
slow += 1
return slow