写在前面
双指针题的显著特点是,题面有相向运行或者同向运动的影子,什么意思呢?比如从序列两头向中间解题(相向,这类型很少出现)和从一端向另一端解题(同向,这类题比较多),往往双指针的题不难,但是若想理清思路,建议多画图。
283. 移动零(双指针)
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
解题思路: 维护两个指针,i指向0,j指向非0,在非0元素前有多少个0,就至少要移动多少次,i.e.,在前面找到一个0元素,这用后面非0元素一步填充,这样能使移动步数最少,OK,算法的大致思路是,当0元素位置在非0元素后面,则说明非0元素不用移动,则下次从0元素后面开始搜索下一个非0元素,若0元素在非0元素前面,则移动非0元素填充0元素位置,下次从下个位置搜索非0元素。这个我思考的解法,看群友们的解法是放在一个循环里,所用的知识依然是Two pointer,但解题思路不同,我们也看看,具体的是,维护两个指针i和j,i在后j在前,只要检测到i所指的元素非0时,就将i和j交换(当然也存在假交换,即i==j),我们会发现,只要i和j不等时,j指向的位置一定是0(原本是0或者交换为0),为什么呢,看下面,中间态2正准备将j和i交换,我们会发现在不停的交换过程中,0会不断后移,而j永远指向第一个0,让后面非0填充它后,再移动一位即可指向下一个“第一个”0,为什么下一个一定是0呢,因为只要i和j不等,i和j之间的元素一定且只能全为0,用0填充中间的间隙。至此,此题的思路就比较灵活了。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int i = 0, j = 0, n = nums.size();
while (i < n && j < n) {
while (i < n && nums[i] != 0) ++i; // 0
while (j < n && nums[j] == 0) ++j; // 1
if (j == n || i == n) break;
if (j < i) {
j = i + 1;
} else {
swap(nums[i], nums[j]);
++j;
}
}
}
};
class Solution {
public:
void moveZeroes(vector<int>& nums) {
for (int i = 0, j = 0; i < nums.size(); ++i) {
if (nums[i] != 0) {
swap(nums[i], nums[j]);
++j;
}
}
}
};
26. 删除排序数组中的重复项
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
解题思路: 这道题也是面试中常考的题,虽然简单但是如果方法使用不当容易导致大量不必要的数据迁移,这题的解题思路其实跟上一题很像,用的也是Two Pointer 解法。OK,解题思路是,因为题目给定的序列已经是有序的了,那么遇到一段连续的相等序列只能取其一,然后题目要求原地删除重复元素,并返回移除后的数组新长度,那么这意味这需要将后面的元素移动至被删除的元素位置,最后要对数组做resize,那么怎么移呢?这是有讲究的,若要使移动次数最少(当然题目没要求),最好是每个后面元素一次移动到位,那我们维护两个指针,指针i指向当前访问的元素,指针j指向上一次被填充的位置(j之后至i之前的这段位置都是需要被填的位置,道理可见上题分析),至此题目可解了。然后在群里看到群友采取的是另一种解法(我先发表观点,不赞成这种解法,之所以列出来讲,是为了说明如果方法不当,会造成大量不必要数据迁移,当然对于本题,这个解法也是对的),每次当检测当当前元素与前一个元素相同,则将当前元素从序列中remove,这解法有什么问题?不难发现,如果题目提供的是链表,这解法将非常完美,但是题目提供的是数组,从中间remove一个元素,后面的元素都会往前挪,因此remove的代价非常高,这里也贴出对应代码,如解法2.
- 解法1
// Tow Pointer,具体的思路是,维护两个指针,i(指向当前访问元素)),
// j(指向与nums[i]相等的最近一次的连续序列第一个元素),i.e.,[j,i]间
// 元素为重复元素,每次将不同元素填入j处
// Time: O(n), space:O(1)
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.empty()) return 0;
int n = nums.size(), i = 1, j = 0;
for (; i < n; ++i) {
if (nums[i] != nums[j]) {
++j;
nums[j] = nums[i];
}
}
nums.resize(j + 1);
return j + 1;
}
};
- 解法2
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.empty()) return 0;
for (int i = 1; i < nums.size();) {
if (nums[i] == nums[i - 1]) {
nums.erase(nums.begin() + i);
} else {
++i;
}
}
return nums.size();
}
};