LeetCode练习——23.移除元素(C++)
题目链接
题目描述
给你一个数组
n
u
m
s
nums
nums和一个值
v
a
l
val
val,你需要原地移除所有数值等于
v
a
l
val
val的元素。元素的顺序可能发生改变。然后返回
n
u
m
s
nums
nums中与
v
a
l
val
val不同的元素的数量。
假设
n
u
m
s
nums
nums中不等于
v
a
l
val
val的元素数量为
k
k
k,要通过此题,您需要执行以下操作:
- 更改 n u m s nums nums数组,使 n u m s nums nums的前 k k k个元素包含不等于 v a l val val的元素。 n u m s nums nums的其余元素和 n u m s nums nums的大小并不重要。
- 返回 k k k。
用户评测:
评测机将使用以下代码测试您的解决方案:
int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
// 它以不等于 val 的值排序。
int k = removeElement(nums, val); // 调用你的实现
assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
assert nums[i] == expectedNums[i];
}
如果所有的断言都通过,你的解决方案将会 通过。
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
提示:
- 0 < = n u m s . l e n g t h < = 100 0 <= nums.length <= 100 0<=nums.length<=100
- 0 < = n u m s [ i ] < = 50 0 <= nums[i] <= 50 0<=nums[i]<=50
- 0 < = v a l < = 100 0 <= val <= 100 0<=val<=100
解题思路
看见这个这个题,第一反应就是直接暴力循环删除数组中的元素,这是最直接也是最简单的解法。题目要求前
k
k
k个元素必须有,这就意味着一旦删除数组中的元素,当前元素后面的所有元素都要往前移动,这必然会产生很大的时间开销。
这里将会展示一种比较快速的方法——双指针法。
另外,题目中给我们提供的是
v
e
c
t
o
r
vector
vector容器,我们可以使用其中封装的方法
e
r
a
s
e
erase
erase来删除指定位置的元素。
暴力循环
对于这个题的暴力循环解法,就是直接嵌套两层
f
o
r
for
for循环,外层
f
o
r
for
for循环用来遍历数组元素,内层
f
o
r
for
for循环用来移动数组元素。(因为题目没有时间复杂度的约束,所以这个题是可以用暴力循环解的)
代码展示:
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; j < size - 1; j++) {
// 后面的元素覆盖前面的元素
nums[j] = nums[j + 1];
}
// 移除元素之后,下标为i的元素是删除的元素的下一个元素,为了保证不遗漏元素,这里需要往前移动一位
// 也就是说此时nums[i]的值是原数组中的nums[i+1],如不将i的值减1,则下一次循环就会跳过这个元素。
i--;
// 删除元素后的数组长度相应地减1
size--;
}
}
// 返回更改后的数组长度,即前k个元素中的k
return size;
}
};
- 时间复杂度: O ( n 2 ) O\left(n^2\right) O(n2)
- 空间复杂度: O ( 1 ) O\left(1\right) O(1)
双指针(也称快慢指针)
针对这个问题,我们定义一个快指针和一个慢指针,前者用来查找新数组的元素,后者指向需要更新数组元素的下标。
这样一来,我们就可以只用一个
f
o
r
for
for循环来完成两个
f
o
r
for
for循环的操作。
class solution {
int removeElement(vector<int>& nums, int val) {
// 定义快慢指针,刚开始都指向第一个元素
int fast = 0;
int slow = 0;
while (fast < nums.size()) {
// 如果发现要删除的元素,则fast后移,slow不动
if (nums[fast] == val) {
fast++;
} else {
// 如果是新数组中的元素,则更新数组同时快慢指针都要后移
nums[slow] = nums[fast];
fast++;
slow++;
}
}
// 最终slow的大小和新数组的长度是相等的,所以只要将slow返回就行了
return slow;
}
};
- 时间复杂度: O ( n ) O\left(n\right) O(n)
- 空间复杂度: O ( 1 ) O\left(1\right) O(1)
利用C++的STL容器中的方法求解
e r a s e ( ) erase() erase()函数的用法: e r a s e ( ) erase() erase()函数用于在顺序型容器中删除容器的一个元素,有两种函数原型:
iteratorerase(iterator position);
iteratorerase(iterator first, iterator last);
第一个删除迭代器
p
o
s
i
t
i
o
n
position
position所指向的元素,第二个删除迭代器
f
i
r
s
t
first
first,
l
a
s
t
last
last所标记的范围内的元素,返回值都是一个迭代器,需要注意的是,该迭代器是指向被删除元素后面的第一个元素。如果删除的是容器的最后一个元素或整个容器被清空,则返回的迭代器将是 end() 迭代器。
如果大家对
e
r
a
s
e
erase
erase方法不是特别了解,可以参考文章【C++】vector中erase用法进行学习。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
// 定义一个vector的迭代器,指向第一个元素
vector<int>::iterator it = nums.begin();
// 遍历数组,nums.end()迭代器指向数组最后一个元素的下一个位置
while (it != nums.end()) {
if (*it == val) {
// 调用erase方法,删除指定位置的元素并将返回的迭代器给it
// 这里执行删除操作后,it将会变成一个野指针,所以需要对it进行更新
it = nums.erase(it);
} else {
// 当前元素不是我们要删除的元素,迭代器需要往后移,继续检查下一个元素
it++;
}
}
// erase内部已经对容器的大小进行了更新,所以我们只需要返回nums.size()就行了
// 注意,这里不能用capacity方法,capacity方法表示的是vector容器的容量,即最多能存储的元素的个数
return nums.size();
}
};
- 时间复杂度: O ( n ) O\left(n\right) O(n)
- 空间复杂度: O ( 1 ) O\left(1\right) O(1)
总结
快慢指针作为一种经典的算法技巧,简单高效,在数组和链表中较为常见。在理解其基本原理和常见应用场景后,还可以通过优化和变种进一步提高其性能。无论是在算法竞赛还是实际项目开发中,掌握快慢指针都将为你提供强大的工具,帮助你高效解决各种复杂问题。
本文相关信息:
参考视频:数组中移除元素并不容易! | LeetCode:27. 移除元素