1. 题目描述
给定一个整数数组 nums 和一个值 val,你需要原地移除数组中所有等于 val 的元素,并返回新的数组的长度。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
- 更改
nums数组,使nums的前k个元素包含不等于val的元素。nums的其余元素和nums的大小并不重要。 - 返回
k。
示例 1:
输入:nums = [3, 2, 2, 3], val = 3
输出:2, nums = [2, 2, _, _]
解释:函数应该返回新的长度 2,并且数组的前两个元素均为 2。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0, 1, 3, 0, 4, _, _, _]
解释:函数应该返回新的长度 5,并且数组的前五个元素均为 0、1、3、0、4。
2. 题解
这道题目要求我们原地移除数组中所有等于 val 的元素。我们可以通过双指针的方法来优化解决。
算法思想:
我们使用双指针来解决这个问题。主要思路如下:
-
left指针:指向当前数组中有效元素的末尾。 -
right指针:用于遍历数组,检查每个元素是否需要保留。
具体的操作步骤是:
-
初始化:
-
left = 0,指向数组的第一个位置,表示当前有效元素的最后一个位置。 -
right = 0,用于遍历整个数组。
-
-
遍历数组:
-
每次循环中,如果
nums[right] != val,则表示该元素不等于val,我们应该保留它。此时将nums[left] = nums[right],并且将left向右移动,表示下一个有效元素的位置。 -
如果
nums[right] == val,则跳过该元素,不进行任何操作。
-
-
返回新数组的长度:
- 最终,
left就是数组中不等于val的元素个数。
- 最终,
通过双指针的方式,我们只需要遍历数组一次,时间复杂度是 O(n),而空间复杂度是 O(1),符合原地修改数组的要求。
代码实现:
java
class Solution {
public int removeElement(int[] nums, int val) {
int n = nums.length; // 获取数组的长度
int left = 0; // 初始化左指针,指向数组的起始位置
// 右指针遍历数组
for (int right = 0; right < n; right++) {
// 如果当前元素不等于 val
if (nums[right] != val) {
nums[left] = nums[right]; // 将当前元素放到左指针的位置
left++; // 左指针右移,指向下一个位置
}
}
return left; // 返回新数组的长度
}
}
3. 与本题算法相关的知识点
双指针技术
双指针是解决数组和链表类问题的一种常见技巧。通过两个指针分别从不同方向遍历或在同一方向上操作,可以高效地解决许多问题。在这道题中,双指针的核心思想是:
-
left指向当前有效数组的最后位置,用来更新不等于val的元素。 -
right用来遍历数组的每个元素,找出不等于val的元素,并把它移到left指针的位置。
双指针技术的优势是空间复杂度为 O(1),即我们在原地修改数组时,不需要额外的空间。这对于节省内存空间以及提高算法效率是非常有帮助的。
1. 左右指针的基本概念:
在编程中,特别是在数组和链表等数据结构中,"指针" 其实并不是指实际的内存地址,而是用来描述"某个位置"或"指向某个位置"的变量。
在 Java 中,我们没有直接的指针类型(不像 C/C++ 中的指针),但是我们可以使用变量(如 left 和 right)来模拟指针的行为,表示数组中元素的位置或下标。
2. 为什么 left 代表"当前数组中有效元素的最后位置"?
在这道题目中,不等于 val 的元素需要移到数组的前面,而且我们需要保持这些有效元素的顺序。
- 我们用
left来标记新数组(去除val后的有效元素)的"末尾"位置。随着遍历数组的进行,left会不断向右移动,表示我们已经"填充"了数组的某些部分。
3. 左右指针是如何一起工作的:
right 指针:
-
right用于遍历整个数组。它会依次访问每个元素。 -
如果当前元素
nums[right]不等于val,我们就将这个元素保留下来,放到left指针的位置,然后将left指针向右移动。
left 指针:
-
left作为有效元素的"末尾"位置,它开始时指向数组的第一个位置(即下标为 0)。 -
当
right找到一个不等于val的元素时,left就会被赋值为该元素,并且left之后会向右移动,准备接受下一个不等于val的元素。
通过这种方式,left 保持指向"新数组"最后一个有效元素的位置,也就是所有不等于 val 的元素都被移动到数组的前面。
4. 具体例子来解释:
假设有如下数组:
java
nums = [3, 2, 2, 3, 4]
val = 3
初始化时:
-
left = 0(指向数组的第一个位置) -
right = 0(指向数组的第一个元素)
然后开始遍历:
-
右指针
right = 0,当前元素是 3,等于val,跳过,不做任何操作。 -
右指针
right = 1,当前元素是 2,不等于val,将nums[left] = nums[right],即nums[0] = 2,然后left++,使得left = 1。 -
右指针
right = 2,当前元素是 2,不等于val,将nums[left] = nums[right],即nums[1] = 2,然后left++,使得left = 2。 -
右指针
right = 3,当前元素是 3,等于val,跳过,不做任何操作。 -
右指针
right = 4,当前元素是 4,不等于val,将nums[left] = nums[right],即nums[2] = 4,然后left++,使得left = 3。
此时数组变成了:
java
nums = [2, 2, 4, 3, 4]
left 最终指向的位置是 3,表示新数组的长度是 3,数组的前 3 个元素 [2, 2, 4] 是有效元素。
5. 为什么移到前面?
这个题目要求的是"原地"修改数组,意思是不能创建新数组,而是要在原数组上操作。因此,我们需要使用 left 指针来控制哪些位置应该填入有效元素。
-
当我们发现一个不等于
val的元素时,我们把它移动到数组的前部。left作为指针,确保我们把这些有效元素填入数组的正确位置。 -
所有原本在数组后面且等于
val的元素,将被自然"丢弃"------因为right遍历整个数组时,它们不会影响left的位置。
原地修改数组
原地修改数组是指在不使用额外数组的情况下,对数组进行修改。在本题中,我们需要确保数组中所有不等于 val 的元素保留在原数组的前面,而不等于 val 的元素被移除。通过双指针的方式,我们将符合条件的元素直接移动到数组的前面,不需要额外开辟新的数组,符合原地修改的要求。
时间复杂度与空间复杂度
-
时间复杂度:
遍历数组一次,时间复杂度为
O(n),其中n是数组的长度。 -
空间复杂度:
我们只使用了常数级别的空间,即
O(1),因为我们没有创建新的数组,而是直接在原数组上进行操作。
4. 总结
这道题目通过使用双指针技术,成功解决了移除元素的问题。我们只需遍历一次数组,时间复杂度为 O(n),空间复杂度为 O(1),是一个高效的解决方案。掌握双指针技术能够帮助我们在处理数组、链表等问题时,避免不必要的空间开销,提高算法效率。
这类题目常见于面试和编程挑战中,熟练掌握双指针技巧对解决许多类似问题至关重要。