本节对应代码随想录中:代码随想录,对应视频链接为:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili
题目链接: 27. 移除元素- 力扣(LeetCode)
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。
示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
你不需要考虑数组中超出新长度后面的元素。
非较优答案,仅为个人思路记录
我的想法是 for 循环从头遍历 nums,并用 res 记录最终结果初始为0。如果当前遍历到的值等于 val,则让 res 位置和遍历到的 val 所在位置替换下,这样一轮 for 循环后前 res 位置都是 val。
新的长度用总长度减去 res 即可。而由于题目要求 nums 前 res 个要返回非 val 的数值,因此还要一轮 for 循环将前 res 个元素和后 res 个元素替换。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size(), res = 0, temp;
for (int i = 0; i < n; i++) {
if (nums[i] == val) {
// 如果和val相等则和让当前值和前面的值替换
temp = nums[i];
nums[i] = nums[res];
nums[res] = temp;
res++;
}
}
// 把和val相等的几个值放到后面
for (int i = 0; i < res; i++) {
temp = nums[i];
nums[i] = nums[n - i - 1];
nums[n - i - 1] = temp;
}
return n - res;
}
};
后来看了题解后,想了下发现前 res 个可以直接删除而不用和后面 res 个替换
nums.erase(nums.begin(), nums.begin() + res);
两层 for 循环,第一层循环从头遍历数组,如果遇到和 val 相等的值,则用第二层 for 循环将当前位置之后的元素都像前移动一位。
例如 0 1 2 3 2 5,val=2
题目中说了不用考虑超过新长度范围外的元素
代码如下:
需要注意的是向前移动一位后,i 要减1,如上面的例子,nums[2]=2,移动后变成0 1 3 2 5,此时若不减1下一次 i=3,将会跳过3与 val 的比较而是比较第二个2与 val。同时 size 减一方便记录新数组的长度。
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;
}
};
与我自己想的解法相比,这个解法在找到 val 时将后面的元素向前移动一位,从而保证前面的元素都是题目要求的。
而我的解法是将找到的 val 放到前面,之后再把他们放到后面(直接放到后面会覆盖后面的元素)。
双指针:通过一个快指针和慢指针在一个for循环下完成两个for循环的工作
快指针从头遍历元素指向当前将要处理的元素,慢指针指向下一个将要赋值的位置。
这样左边的元素都不等于 val
与前面的两个解法相比,用慢指针替代了前面的第二个 for 循环。关键语句是 nums[slowIndex++] = nums[fastIndex];
将后面的值直接复制到前面合适的位置。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (nums[fastIndex] != val) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
这里的快指针相当于常写的 for (int i = 0; i < size; i++)
中的 i 一样,遇到和 val 相等的值时,再用一个慢指针将和 val 相等的值移到前面。如果将 fastIndex 改成常写的 i 就会发现其实就是用 nums[slowIndex++] = nums[fastIndex];
解决了上面两种解法好几行才能解决的问题。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] != val) {
nums[slowIndex++] = nums[i];
}
}
return slowIndex;
}
};
当移除的元素在开头时,我们要将每个元素都左移一位。如[1,2,3,4,5],val=1。实际上只要将右边的5移到左边的1的位置即可。
可以使用两个指针,分别位于数组的左右两端,如果左指针等于 val 则将右指针指向的元素复制到左指针的位置。直到两个指针重合为止。
需要注意的是如果写成 right = nums.size()-1
那 while 的条件是 <=
。原因是最后返回的是 left 值,当 left=right 时,如果此时 nums[left] != val
还是要将 left+1。如 nums={3,2,2,3},val=3
时,当 left=right=1,nums[1]=2时,还是要执行一次 while 让 left+1,从而返回 left=2,如果 while 写的 <
那就会错误的返回 left=1
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0, right = nums.size()-1;
// 注意是<=
while (left <= right) {
if (nums[left] == val) {
nums[left] = nums[right];
right--;
} else {
left++;
}
}
return left;
}
};
总结: