看到有
数组
、原地
、数组可变
等关键字,一般都是用双指针扫!
题目要求
- 给你一个数组
nums
和一个值val
,你需要原地
移除所有数值等于val
的元素,并返回移除后数组的新长度。 - 不要使用额外的数组空间,你必须仅使用
O(1)
额外空间并 原地 修改输入数组。 - 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。
例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
复制代码
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
复制代码
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
复制代码
解题思路
看到数组题目,划重点 原地修改数组
、空间复杂度不能超过O(1)
、不考虑超出新长度后元素
。
原地修改数组
:意思是以后的元素所在的位置可以重新赋值为其他元素。空间复杂度不能超过O(1)
:不能开辟新的内存空间。不考虑超出新长度后元素
:新长度后面的元素可以随意操作。
所以首先想到的解法是维护两个指针 fast
、slow
,快指针扫描符合条件的元素,慢指针负责对新数组进行赋值和记录数组长度。
即:当快指针扫描到不是目标元素的值,慢指针当前位置的元素重新赋值为快指针扫描到的元素。
看代码【暴力解法】:
class Solution {
public int removeElement(int[] nums, int val) {
if (nums == null) {
return 0;
}
// 定义慢指针
int slow = 0;
int len = nums.length;
// 使用快指针遍历数组
for (int fast = 0; fast < len; fast++) {
int value = nums[fast];
if (value != val) {
// 快指针扫描到了符合插入条件的元素,使用慢指针存储
nums[slow] = value;
// 慢指针右移
++slow;
}
}
// 返回慢指针,此时新数组的长度就是慢指针所在的位置
return slow;
}
}
复制代码
算法优化
看到上述的方法,我们会想,还有更好的方法吗?
对于上述的算法,两个指针同时从左侧向右扫描,但是我们发现,如果对于数组 [1,2,3,4,5,6]
,目标值 1
的时候,后五个元素并没有任何变化,我们可以想到直接将元素 6
替换 1
得到新的数组,并记录长度为 5
。
所以上述算法可以优化为:维护两个指针(left
、right
),一个从左向右判断并记录长度,一个从右向左移动。
使用 left
指针遍历数组,扫描到不符合插入条件(指向元素等于目标值)的,将 right
指向的元素拿过来, right
左移。符合插入条件(指向元素不能与目标值)的 left
右移。一直扫描到两个指针重合。结束遍历,此时 left
的位置就是新数组的长度。
代码如下:
class Solution {
public int removeElement(int[] nums, int val) {
if (nums == null) {
return 0;
}
// 定义左指针
int left = 0;
// 定义右指针
int right = nums.length - 1;
// 遍历数组
while(left <= right) {
if (nums[left] == val) {
// 扫描到不符合条件的,将左指针指向的元素赋值为右指针指向的元素
nums[left] = nums[right];
// 右指针左移
--right;
} else {
// 符合插入条件,左指针右移
++left;
}
}
// 左指针所在的位置就是新数组的长度
return left;
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END