大家好,我是怒码少年小码。
使用数组时常常会遇到大量移动元素的操作,例如删除和插入,这就很让人头疼了,那么我们该怎么解决呢?这里介绍一个方法——双指针思想。
双指针思想
所谓双指针其实就是两个变量,不一定真的是指针。常见的有快慢指针、相撞型指针、背向型指针等等。常用于处理数组、字符串等问题。下面举两个应用实例你就会爱上它了😎。
删除元素专题
1.原地移除所有数值等于val的元素
LeetCode 27: 给一个数组nums和一个值val,原地移除所有等于val的元素,并返回新数组的长度。要求:仅使用O(1)额外空间并原地修改输入数组。元素的顺序可以改变。
示例1:
- 输入:nums=[3,2,2,3],val=3
- 输出:2,nums=[2,2]
示例2:
- 输入:nums=[0,1,2,2,3,0,4,2],val=2
- 输出:5,nums=[0,1,4,03]
方法一:快慢指针
基本思想就是定义连个指针slow和fast,初始值为0,两指针都从第一个元素开始。用slow来记录有效的不需要删除的部分,fast表示当前要访问的元素。
fast不断向后移动:
- 如果
nums[fast]!=val
,则当前元素是要保存的元素,把nums[fast]
移动到nums[slow]
,然后slow++
(更新下一个要保存的元素的位置)和fast++
(继续遍历) - 如果
nums[fast]==val
,则当前元素是要删除的元素,于是继续fast++
往后走。
int removeElement(int nums[],int size, int val) {
int slow = 0;
for (int fast = 0; fast < size; fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
//输出检测
cout << slow << endl;
for (int i = 0; i < slow; i++) {
cout << nums[i] <<",";
}
cout << endl;
return slow;
}
最后slow
的值恰好和数组长度相同,妙啊😊!
方法二:对撞指针
也叫交换移除,基本思想是从右侧找到不是val的值来顶替左侧是val的值。什么意思?看图:
int removeElement02(int nums[],int size, int val) {
int right = size - 1;
int left = 0;
for (left = 0; left <= right;) {
if (nums[left] == val && nums[right] != val) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
if (nums[left] != val) {
left++;
}
if (nums[right] == val) {
right--;
}
}
//输出检测
cout << left << endl;
for (int i = 0; i < left; i++) {
cout << nums[i] << ",";
}
cout << endl;
return left;
}
首先定义两个指针left
、right
位于数组的首位。left
用于记录不用删除的新数组元素,如果遇到要删除的元素就从右边找到不用删除的元素覆盖,否则就left++
继续走,所以这里的left++是有条件的⭐。left==right
循环结束后,left 的值即为新数组的长度。
2. 删除有序数组中的重复项
LeetCode 26 : 给一个有序数组nums,请原地删除重复出现的元素,使得每个元素只出现一次,返回新数组的长度。
示例1:
- 输入:nums=[1,1,2]
- 输出:2,nums=[1,2]
示例2:
- 输入:nums=[0,0,1,1,1,2,2,3,3,4]
- 输出:5,nums=[0,1,2,3,4]
这里还是用双指针最方便,一个fast
用于负责遍历数组,一个slow
用于指向下一个不重复元素应该要插入的位置。
int removeDuplicates(int nums[], int size) {
int slow = 1;
int fast ;
for (fast = 1; fast < size; fast++) {
if (nums[fast] != nums[slow - 1]) {
nums[slow] = nums[fast];
slow++;
}
}
//输出检测
cout << slow << endl;
for (int i = 0; i < slow; i++) {
cout << nums[i] << ",";
}
cout << endl;
return slow;
}
为了减少不必要的操作把slow
设置为1,从第二个元素开始,让nums[fast]
和 nums[slow - 1]
作比较, nums[slow - 1]
表示已经插入的不重复的元素,只有和这个不一样才能插入。因为本题的背景是有序数组,所以不用担心,原数组相同元素不连续的问题。
元素奇偶移动专题
LeetCode 905 : 按奇偶排序数组,给定一个非负整数数组nums,返回一个数组,这个数组中nums的所有偶数元素后跟着所有奇数元素。元素之间的顺序不做要求。
示例:
- 输入:[3,1,2,4]
- 输出:[2,4,1,3]
最简单的肯定是直接创建一个新数组,然后遍历根据条件放置元素啦。那么如果面试官要求空间复杂度为O(1),阁下又该如何应对😁?
答案还是双指针。维护两个头尾指针left
和right
,left从0开始检查当前元素是否为偶数,是就继续,不是就停下;right从size-1开始检查当前元素是否为奇数,是就继续,不是就停下;然后交换nums[left]
和nums[right]
。直到left>=right
。
int* sortArrayParity(int* nums, int size) {
int left = 0;
int right = size - 1;
while (left < right) {
if (nums[left] % 2 == 1 && nums[right] % 2 == 0) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
if (nums[left] % 2 == 0) {
left++;
}
if (nums[right] % 2 == 1) {
right--;
}
}
//输出检测
for (int i = 0; i < size; i++) {
cout << nums[i] << ",";
}
cout << endl;
return nums;
}
因为这里要返回的是一个数组,所以使用指针传值。
END
这篇下来我真的觉得双指针真香啊~😊