数组——移除元素
题目
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/remove-element
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
1.暴力解法
暴力解法就是用两个for循环,一个循环遍历数组中元素,找值为val的数组元素,另一个循环完成数据元素的移动,覆盖掉值为val的元素,也就相当于把这个元素删除了。
这种写法是我比较习惯的,不知道为什么,涉及到这种移动覆盖的,我就喜欢从后面来。代码随想录里,卡尔哥的写的从前面来的,具体有点小细节还不太一样。
//从后往前遍历数组 时间复杂度:O(n^2)
class Solution {
public int removeElement(int[] nums, int val) {
int length = nums.length;
for(int i = length - 1; i >= 0; i--){
if(nums[i] == val){
for(int j = i + 1; j < length; j++){
nums[j - 1] = nums[j];
}
length--;
}
}
return length;
}
}
// 前往后便利数组
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
for (int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
return size;
}
};
注意:
- 元素下标问题:
前后遍历的区别,就在 i 的处理上,从后向前方式因为每次循环都是减1,所以只需要每次移动之后,length–就行了,下次直接从当前元素的前一位置判断;而从前向后遍历时,每次循环结束 i 会加1,而因为 i 之后的元素都已经向前移动了一位,所以必须在每次移动结束后,执行 i-- ; 才不会漏判元素。 - 数据越界问题:
这里的元素覆盖,开始我写的是
for(int j = i; j < length; j++){
nums[j] = nums[j+1];
}
length--;
}
但是这样就出错了,因为当i为数组最后一个元素,nums[j+1]
就越界了,所以要写下面这样:
for(int j = i + 1; j < length; j++){
nums[j - 1] = nums[j];
}
length--;
}
暴力方式总结
因为这个题要求在原数组上修改,所以随着数组元素的移动,数组实际长度其实不变的,数组初始化的时候在内存中申请的一片地址连续的空间而是需要我们定义一个int类型的length保存数组长度,然后每删除一个val,length就减一,最后返回的length就是所谓的新数据长度,其实就是说,执行完这些操作后,数据中的前length个元素,就是没有val的元素了,length后的元素不用管。一个暴力解法啰嗦了这么多,我觉得对我来说还是有必要的,因为自己太菜了。
2.双指针
class Solution {
//时间复杂度O(1)
public int removeElement(int[] nums, int val) {
int slow = 0;
for(int fast = 0;fast < nums.length;fast++) {
if(val != nums[fast]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
}
双指针,可以用一个循环,完成两个循环的操作,时间复杂度降为O(n)。
思想:
我们定义一个slow指针(这里是数组下标),初始值为0,还有一个fast指针,初始值也为0;for循环里用fast < nums.lenth 为条件。
- 当nums[fast] 不等于val时,执行
nums[slow++] = nums[fast];
这条语句实现的是,当数组中下标 fast 的值不为 val时,就将下标为fast的元素之赋给下标为slow的元素,并且slow向右移动一位,fast++也会让fast指针右移一位;所以当fast的位置不为val时,程序执行效果就是slow和fast共同右移; - 当nums[fast]当值为val时,if语句不执行,fast自己右移一位,slow不动,当右移后的位置又不等于val时,继续上面的步骤;这里注意,当fast的值为val时,slow和fast下标相同,所以fast自己向前移动,去找不等于val的元素,而slow停在原地,等着fast找到不等于val的元素后,把这个找到的值赋值给slow的位置,也就完成了值为val元素的删除;
- 我们可以分析,不论什么情况,只要fast达到了数组尾端,那么slow之前的元素都是值不为val的元素,注意,不包含最后的slow下标的元素,因为slow停留位置有两种情况:要么是val元素的位置,要么是将val元素覆盖后的下一位置(因为赋值操作是
nums[slow++] = nums[fast];
,每次赋值后,slow都会前移)。所以结论就是,slow之前的元素就是我们删除了val元素剩下的元素,也就是题目要求的新数组。那么我们只要返回slow的值就可以了,因为新数组的长度就是slow(数组下标从0开始)。 - 最后补充一下,leetcode这道题下面的说明写的很清楚,我们返回值为一个整数,但是leetcode输出的是一个新组,原理就是只返回操作后的数组前slow个元素。