要求删除给定数组中和val大小一样的元素,返回删除后数组的大小,同时原数组中的元素得删除掉。这表明不能直接一个遍历,直接返回原数组大小-元素个数。
看到这题我的第一个思路:
新建一个数组,把原数组中符合条件的元素添加进去。再让原数组等于新数组。
代码如下:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
vector<int> res;
for(auto x: nums){
if(x!=val){
res.push_back(x);
}
}
nums = res;
return nums.size();
}
};
思路非常简单,暴力。时间复杂度为O(N)空间复杂度为O(N)
一般要新建一个vector再将新vector赋值给老vector的题都可以使用双指针法,双指针法可以将空间复杂度降为O(1)。
双指针法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0,right = nums.size()-1,count =0;
while(left<=right){
if(nums[left]==val){
swap(nums[left],nums[right]);
right--;
count++;
}else{
left++;
}
}
return nums.size()-count;
}
};
因为这题只考虑返回的原数组前k(原数组个数-和val一样的元素个数)个值是否和要求一样,并且没有排序要求,所以可以直接使用swap在原数组上通过双指针将与val大小一样的元素移动到数组尾部。如果数组尾部也是一个和val大小一样的元素,那swap这两个数的意义在于用count记录了swap的次数,swap过后通过right--来将指针从数组尾部往前进一个,代表着swap过来的数不会在被我们考虑到。而被swap到前面的数如果value也与val一样的话,则会将它和排在数组倒数第二位置的数交换,而不会进入两个数都等于val的swap死循环。
重读完题干后我发现这道题的空间复杂度本来的要求是O(1),不知道我的第一种方法是怎么通过的。看了carl哥的文章(代码随想录),自己重写了一个符合空间复杂度为O(1)的brute force解法。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
//space should be O(1)
int count =0;
int i=0;
while(i<nums.size()){
//如果i位置的元素与val大小一样
if(nums[i]!=val){
i++;
}else{
int j = i;
while(j<nums.size()-1){
nums[j]=nums[j+1];
j++;
}
nums[nums.size()-1]=-1;
count++;
}
}
return nums.size()-count;
}
};
有一个不足之处是当元素大小为val时我将整个后面部分往前移动一步时 ,给最后一个元素补充的数值为-1, 如果测试数据中存在-1的元素时则会陷入死循环。
优化:每当把元素整体往前提一个的时候,直接把数组的总长度缩小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];
}
size--;
i--;
}
}
return size;
}
};
这个方法不存在元素得大于0的问题,因为使用size作为遍历的区间。当我取出元素时,会将size-1所以实际上只用size内的部分。另一个小技巧则是在for循环中可以使用i--来再次确定提到前面的元素是不是与val相当需要取出的情况,这个技巧我之前是没有见过的。
看完carl哥的双指针法思路写出的代码:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow=0,fast=0,count=0;
while(fast<nums.size()){
if(nums[fast]!=val){
nums[slow]=nums[fast];
fast++;
slow++;
}else{
//fast指向的数和val相同
fast++;
count++;
}
}
return nums.size()-count;
}
};
代码随想录中简化后的代码:
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
化简的代码思路是:无论如何fast指针要往前移动,放在for循环里,而slow指针只有在fast指向不为val时才移动,同时更新slow。使用后置运算符实在是妙!