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. 移除元素

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值