最近在准备算法面试时,遇到了一个看似简单但容易踩坑的数组操作问题:如何在不使用额外空间的情况下,原地移除数组中所有等于特定值的元素,并返回新数组的长度。这个问题在LeetCode上编号27,属于数组类基础题型,但实际编码时会发现不少细节需要处理。
举个例子,给定数组 nums = [3,2,2,3] 和 val = 3,函数应该返回长度2,且数组前两个元素应为[2,2]。原题要求必须修改输入数组,并使用O(1)额外空间完成操作。这对C++选手来说是个很好的指针运用练习。
最直观的想法是遇到目标值就整体前移元素:
cpp复制for (int i = 0; i < size; i++) {
if (nums[i] == val) {
for (int j = i; j < size - 1; j++) {
nums[j] = nums[j + 1];
}
size--;
i--; // 重要!因为后续元素已经前移
}
}
这种方法时间复杂度O(n²),在数据量大时性能较差,但优点是思路直接,适合初次理解问题。
更高效的解法是使用快慢双指针:
cpp复制int slow = 0;
for (int fast = 0; fast < nums.size(); fast++) {
if (nums[fast] != val) {
nums[slow++] = nums[fast];
}
}
这种写法时间复杂度O(n),空间复杂度O(1),是面试官最期待的解法。关键在于理解两个指针的分工与协作。
cpp复制int removeElement(vector<int>& nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.size(); fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
实际编码时需要特别注意:
测试用例建议:
- 常规情况:[0,1,2,2,3,0,4,2], val=2
- 全匹配情况:[3,3,3], val=3
- 无匹配情况:[1,2,3], val=4
- 空数组:[], val=0
原题不要求保留元素原始顺序,若要求保留,可以采用交换法:
cpp复制int left = 0, right = nums.size();
while (left < right) {
if (nums[left] == val) {
nums[left] = nums[right - 1];
right--;
} else {
left++;
}
}
这种方法减少了赋值次数,但改变了非目标元素的相对位置。
将双指针模式抽象为通用模板:
cpp复制int slow = 0;
for (int fast = 0; fast < nums.size(); fast++) {
if (/* 满足条件 */) {
nums[slow++] = nums[fast];
}
}
类似结构可用于:去重、移动零等问题。
调试技巧:
- 在循环中加入打印语句观察指针变化
- 使用小型测试用例手动模拟执行
- 特别注意循环终止条件
在LeetCode测试环境中:
虽然时间复杂度相同,但实际运行中双指针法常数项更小。当数组长度超过1e5时,差异会非常明显。
在实际项目中,这种原地操作的思想可以应用于:
关键是要明确是否真的需要原地修改,因为这会破坏原始数据。在多人协作的项目中,需要做好文档说明。