题目内容
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间原地修改输入数组。
元素的顺序可以改变,不需要考虑数组中超出新长度后面的元素。
- nums = [3,2,2,3] val = 3, 返回长度2,并且nums的前两个元素均为2
- nums = [0,1,2,2,3,0,4,2], val = 2,返回长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4 看完题,想起跟前一题差不多,只不过26题的目的在于删除重复项,而这里27题在于返回数组在去掉目标值之后的长度。
此外,可以注意到题目中有三个关键点,原地,顺序可改变,不用考虑新长度后面的元素。原地是要求,后两个提示用上了就应该是好解法。
解法一,遇到就删除
这个解法逻辑和实现都很简单,就是遇到目标val
,就直接使用splice
方法删除即可。这里需要注意的是,计数变量的累加是要在当前值不等于目标才加,因为splice会直接改变原数组。如果删完就加一,会导致连续的目标值无法被删除。
但刚刚说到的两个提示都没用上,且这个解法稀松平常是人都能想到。肯定还有更妙的解法。通过提交的反馈也可以看到,这个解法时间复杂度略微高了点。
var removeElement = function(nums, val) {
for (let i = 0; i < nums.length; ) {
if (nums[i] === val) {
nums.splice(i, 1)
} else {
i++
}
}
return nums.length
};
解法二,排序后一次性删除
这个解法,是想到Array对象有indexOf
和lastIndexOf
方法可以分别获取元素首次和最后一次出现的下标。由此,先对数组排序就可以在获得开始和结束坐标后一次性删除。
但本质上跟前一种解法没差,都是找到目标然后删除。同时,仍然没用上后两个提示。
这个解法,时间复杂度会相对好点,但是空间复杂度就不行了,因为计算了开始坐标和结束坐标。
var removeElement = function(nums, val) {
nums.sort()
if (nums.indexOf(val) !== -1) {
nums.splice(nums.indexOf(val), nums.lastIndexOf(val) - nums.indexOf(val) + 1)
}
return nums.length
};
解法三,双指针法
双指针法的逻辑是,初始化有效位子变量i为0;使用循环,当当前数字不等于目标数字的时候,执行拷贝当前元素至当前有效位置;如果相同的时候,则跳过该数字不进行拷贝。
循环完毕,i
即为答案数组的长度。一个循环,做了两件事。
在解法一中,我们用一次循环和splice实现目标元素的移除,这里的双指针法同样是使用循环,只不过多使用了一个变量来维护当前的有效位置。不同之处在于splice
方法和拷贝覆盖操作的效率。
同时,这个解法也用上了前面说的两个提示,时间复杂度击败率为99.2%。
let i = 0;
for (let j = 0; j < nums.length; j++) {
if (nums[j] != val) {
nums[i] = nums[j];
i++;
}
}
return i;