作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级
问题描述
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例
输入: nums = [3,2,2,3], val = 3 输出: 2, nums = [2,2]
解释:
函数应返回新的长度 2, 并且 nums 中的前两个元素均为 2。
不需要考虑数组中超出新长度后面的元素。
双指针法
这个问题可以用“双指针法”来解决,特别是“快慢指针法”非常适用。该方法的基本思想是使用两个指针,一个快指针(fast)和一个慢指针(slow),快指针用于遍历数组,慢指针用于跟踪不等于 val 的元素应该插入的位置。
算法步骤
- 初始化:设 slow = 0,fast 从 0 开始。
- 遍历数组:
• 如果 nums[fast] 不等于 val,将 nums[fast] 复制到 nums[slow],然后 slow 向前移动一位。
• fast 指针每次循环都向前移动一位。 - 返回结果:遍历结束后,slow 指针的位置即为新的数组长度。
算法分析
时间复杂度:O(n),其中 n 是数组的长度,因为我们需要遍历数组一次。
空间复杂度:O(1),因为我们只使用了常数级别的额外空间。
代码实现
def removeElement(nums, val): slow = 0 for fast in range(len(nums)): if nums[fast] != val: nums[slow] = nums[fast] slow += 1 return slow # 调用函数示例 nums = [3, 2, 2, 3] val = 3 new_length = removeElement(nums, val) print("New length:", new_length) print("Updated array:", nums[:new_length])
输出
New length: 2 Updated array: [2, 2]
图解双指针
为了更清晰地展示双指针法在“移除元素”问题中的应用,我们将使用数组 nums = [3, 2, 2, 3] 和值 val = 3 作为例子,进行详细的 ASCII 图表展示。我们的目标是移除数组中所有的 3,并返回新数组的长度。
初始状态
数组和指针初始布局如下:
nums = [3, 2, 2, 3] ^ | slow & fast (初始化在0)
迭代过程
Step 1: fast = 0, nums[fast] = 3
nums = [3, 2, 2, 3] ^ | slow & fast - nums[fast] == val (3 == 3),仅移动 fast 指针。
Step 2: fast = 1, nums[fast] = 2
nums = [3, 2, 2, 3] ^ ^ | | slow fast - nums[fast] != val (2 != 3),复制 nums[fast] 到 nums[slow],然后两者各自前进一位。
复制后的数组状态:
nums = [2, 2, 2, 3] // 将 nums[fast] 的值复制到 nums[slow] ^ ^ | | slow fast
Step 3: fast = 2, nums[fast] = 2
nums = [2, 2, 2, 3] ^ ^ | | slow fast - nums[fast] != val (2 != 3),同理复制并移动两个指针。
复制后的数组状态(虽然本步骤不涉及真实的复制操作,因为元素已经是目标值):
nums = [2, 2, 2, 3] ^ ^ | | slow fast
Step 4: fast = 3, nums[fast] = 3
nums = [2, 2, 2, 3] ^ ^ | | slow fast - nums[fast] == val (3 == 3),仅移动 fast 指针。
结束后
遍历结束,slow 指针位置在第二个 2 后面,标示新数组的长度为 2:
Final Array = [2, 2] Length = 2
ASCII 结果展示
nums = [2, 2, 2, 3] // 最终修改过的数组状态 ^ ^ | | slow fast // 指针位置示意
通过这种详细的步骤展示,可以清楚地看到双指针法如何有效地在原地修改数组以移除特定元素,同时保证算法的空间效率。这种方法不仅直观,而且易于实现,非常适合解决类似的数组处理问题。
其他方法
除了双指针法外,还有几种其他方法可以用来解决“移除元素”这个问题。虽然双指针法由于其高效性和符合题目要求的原地修改特性而常被推荐,但探索其他方法可以提供额外的见解和应用广度。
1. 使用 Python 特有的方法
虽然这种方法可能不满足 O(1) 额外空间的要求,但它在某些编程环境中可以提供快速简单的解决方案:
def removeElement(nums, val): while val in nums: nums.remove(val) return len(nums)
这种方法直接利用了 Python 列表的 remove() 方法,该方法会移除列表中第一个匹配的元素。循环确保所有相等的元素都被移除。但需要注意,remove() 方法的时间复杂度为 O(n),因此在最坏情况下,这种方法的时间复杂度为 O(n^2)。
2. 计数和覆盖
此方法同样使用 O(1) 空间,适用于数组操作,尤其当数组大部分元素都需要保留时效率较高:
def removeElement(nums, val): count = 0 for num in nums: if num != val: nums[count] = num count += 1 return count
这里,count 作为数组的索引,仅在当前元素不等于 val 时递增,相当于“慢指针”在双指针方法中的作用。每个不等于 val 的元素都被复制到 nums[count],从而覆盖掉等于 val 的元素。
3. 优化的双指针法(当要删除的元素很少时)
当数组中待删除 val 的元素相对较少时,可以优化双指针法以减少不必要的操作:
def removeElement(nums, val): left, right = 0, len(nums) - 1 while left <= right: if nums[left] == val: nums[left], nums[right] = nums[right], nums[left] right -= 1 else: left += 1 return left
在这种方法中,left 和 right 分别为数组的头部和尾部指针。如果左指针 left 遇到了 val,就将其与右指针 right 指向的元素交换,并向内移动 right 指针。这样可以保证数组左侧不含有 val,而右侧包含所有的 val。这种方法减少了不必要的复制操作,特别是当 val 元素少时更有效。
结论
每种方法都有其适用场景和优缺点。选择正确的方法依赖于具体问题的需求,如对时间和空间效率的要求、编程语言的特性等。理解多种方法可以提供更灵活的工具集,以应对各种不同的编程挑战。
欢迎关注微信公众号 数据分析螺丝钉