1. 双指针思想
双指针就是两个变量,不一定真的是指针。双指针思想适用于处理数组、字符串等场景问题。
例如:从下列序列中删除重复元素[1,2,2,2,3,3,3,5,5,,7,8],重复元素只保留一个。删除之后的结果应该为[1,2,3,5,7,8]。可以在删除第一个2时将其后面的元素整体向前移动一次,删除第二个2时,再将其后的元素整体向前移动一次,处理后面的3和5都一样的情况,这就导致需要执行5次大量移动才能完成,效率较低。
使用双指针可以方便的解决这个问题,如图:
首先定义两个指针slow、fast。
slow表示当前位置之前的元素都是不重复的,而 fast 则一直向后找,直到找到与 slow 位置不一样的。找到之后就将 slow 向后移动一个位置,并将 arr[fast] 复制给 arr[slow] ,之后 fast 继续向后找,循环执行。找完之后 slow 以及之前的元素就都是单一的了。这样就可以只用一轮移动解决问题。
如上这种一个在前一个在后的方式也称为快慢指针,有些场景需要从两端向中间走,这种成为对撞指针或者相向指针。
还有一种背向型指针,就是从中间向两边走。
2. 删除元素专题
2.1 原地移除所有数值等于 val 的元素
LeetCode27:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
要求:
1.不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
2.元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
例子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,0,3]
关键点:如果有很多值为val的元素的时候,如何避免反复向前移动?
快慢双指针
定义两个指针slow和fast,初始值都是0.
slow之前的位置都是有效部分,fast表示当前要访问的元素。
这样遍历的时候,fast不断向后移动:
- 如果 num[fast] 的值不为 val ,则将其移动到 nums[slow++] 处。
- 如果 nums[fast] 的值为 val ,则 fast 继续向前移动,slow 先等待。
这样,前半部分是有效的,后半部分是无效的。
/**
*
* @param nums 数组
* @param val 需要删除的值
* @return
*/
public static int removeElement(int[] nums, int val) {
int slow = 0;
for (int fast = 0;fast<nums.length;fast++){
if (nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
//最后剩余元素的数量
return slow;
}
2.2 删除有序数组中的重复项
LeetCode26:给你一个 升序排列 的数组 nums ,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的相对顺序应该保持 一致 。然后返回 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]
一个指针负责数组遍历,一个指向有效数组的最后一个位置。并且令 slow = 1,并且比较的对象换做 nums[slow-1] ,如图:
最后返回 slow 的值 2,表示修改后的数组长度为2。
最终结果为:[1,2]。
ps:上述操作中,并没有真正删除数组中的元素,知识通过slow的位置来标识新数组的长度,原数组中的值仍然存在,只是在 slow 之后的位置不在有有效值。
代码如下:
public static int removeDuplicates(int[] nums) {
//slow表示可以放入新元素的位置,索引为0的元素不用管
int slow = 1;
//循环起到了快指针的作用
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != nums[slow - 1]){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
进阶版:
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例1:
输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
示例2:
输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
以示例1为例,步骤如图:
代码实现:
public static int removeDuplicatesPlus(int[] nums) {
//最大重复次数2次
int maxRepeat = 2;
//慢指针slow 指向索引为1的位置
int slow = maxRepeat -1;
for (int fast = maxRepeat; fast < nums.length; fast++) {
//nums[fast] != nums[slow - maxRepeat + 1]
//保证区间[0,slow] 中元素最多不会超过2次
if (nums[fast] != nums[slow - maxRepeat +1]){
//先扩展区间
slow++;
//再赋值
nums[slow] = nums[fast];
}
}
return slow + 1;
}
3. 元素奇偶移动专题
LeetCode905,按奇偶排序数组。
给你一个整数数组 nums
,将 nums
中的的所有偶数元素移动到数组的前面,后跟所有奇数元素。
返回满足此条件的 任一数组 作为答案。
例如:
输入:[3,1,2,4]
输出:[2,4,3,1]
输出 [4,2,3,1] , [2,4,1,3] , [4,2,1,3] 也会被接口
最简单实现:使用一个临时数组,第一遍查找并将所有的偶数复制到新数组的前部分,第二遍查找并复制所有的奇数到数组后部分。但是空间复杂度较高。
对撞型双指针方法,如图:
代码如下:
public static int[] sortArrayByParity(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right) {
if (nums[left] % 2 > nums[right] % 2){
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
if (nums[left] % 2 == 0) left++;
if (nums[right] % 2 == 1) right--;
}
return nums;
}