快慢指针
26. 删除有序数组中的重复项
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
思路
使用两个指针进行遍历数组
i 指针指向要返回位置的下一个,另一指针 j 遍历整个原数组
只有当 i 和 j 指向的数不同时,长度加一,即 i++,并将 j 指向的数存入要返回的数组长度中
代码
public int removeDuplicates(int[] nums) {
int i = 0;
for(int j = 0; j < nums.length; j++) {
if(nums[i] != nums[j]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
}
通解解法
如果题目从:每个元素最多出现一次 改为 最多出现k次,那么需要类比一次可以想出以下两点
- 最多出现k次,那么前k位数应该直接保留
- 对于后面的数字,它能够保留的前提是:与前面第 k 个数比较,不同则保留
例如:k = 1,假设有:[3,3,3,3,4,4,4,5,5,5]
- 首先设定idx = 0,即要插入的位置,目标数组为[]
- 先插入前1个数字,即idx = 1,目标数组为[3]
- 与前面1个数字比较,直到x = 4时才不同,此时idx++,idx = 2,目标数组为[3,4]
- 继续与前1个数字比较,直到x = 5时才不同,此时idx++,idx = 3, 目标数组为[3,4,5]
- 继续遍历直到退出循环,返回idx
代码
public int removeDuplicates(int[] nums) {
return process(nums, 1);
}
public int process(int[] nums, int k) {
int idx = 0;
for(int x : nums) {
if(idx < k || nums[idx - k] != x) nums[idx++] = x;
}
return idx;
}
80. 删除有序数组中的重复项 II
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
思路
有了上面的通解解法,此题直接改变k = 2即可
例如数组:[1,1,1,2,2,3]
- 首先设定idx = 0,即要插入的位置,目标数组[]
- 加入前2个数字,idx = 2,目标数组[1,1]
- 然后与前第2个数字比较,发现1 == nums[idx - 2] ,跳过此数,idx = 2,目标数组[1,1]
- 继续与前第2个数字比较,发现2 != nums[idx - 2],此时加入该数,idx = 3,目标数组[1,1,2]
- 2 != nums[idx - 2] ,nums[idx] = 2,即将2加入, idx++,idx = 4,目标数组[1,1,2,2]
- 3 != nums[idx - 2] ,nums[idx] = 2,即将3加入,idx++,此时原数组遍历完毕,退出循环,idx = 5, 目标数组[1,1,2,2,3]
代码
就将上一题的k = 1改成了k = 2
public int removeDuplicates(int[] nums) {
return process(nums, 2);
}
public int process(int[] nums, int k) {
int idx = 0;
for(int x : nums) {
if(idx < k || nums[idx - k] != x) nums[idx++] = x;
}
return idx;
}
27. 移除元素
https://leetcode-cn.com/problems/remove-element/
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路
本题也是双指针的使用,只是 保留条件 发生了改变
依然尝试使用通解解法,只不过 每个元素不允许重复出现 k 次 变为 不允许出现指定元素
代码
public int removeElement(int[] nums, int val) {
return process(nums, val);//此时的val不代表允许重复的个数了,而是不允许出现的数字
}
public int process(int[] nums, int val) {
int idx = 0;
for(int x : nums) {
if(x != val) nums[idx++] = x;
}
return idx;
}
快慢指针总结
- 双指针中的快慢指针通常是:i 指针保留答案,j 指针遍历数组,一快一慢
- 通用解法是对 「数据有序」,「相同元素保留k位」 更为本质的解法,主要 考虑在这两点相同的情况下使用
- 快慢指针中 i 用来指向目标数组, j 用来遍历数组,所以 j 指针可以使用 foreach 循环代替,只需要每次条件成立时改变 i 指针即可
- 对于「每个元素最多重复k次」和「不允许出现指定元素」只是 元素保留条件发生了改变, 此类题目中,解决过程就是将「保留逻辑」应用到遍历的每一个位置
滑动窗口
思想
滑动窗口:(类似于双指针,不过更像一个窗口的移动)
不断调节子序列的起始位置和终止位置,从而得出我们想要的结论
思路
解决滑动窗口问题主要确定三点
- 窗口是什么
- 如何移动窗口的右边界
- 如何移动窗口的左边界
一般来说,指向窗口的结束位置的指针都是遍历整个数组,而指向窗口的起始位置的指针一般都是根据约束来移动
时间复杂度
一般都是O(n),因为对于每个元素,他只有 进入窗口 和 退出窗口 两次被操作的机会,所以时间复杂度是 2 * n,而不是 n^2
模板
for(枚举选择)
右边界
while(不符合条件)
左边界
更新结果(根据题意放在while中还是while后)
参考
宫水三叶:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/solution/shua-chuan-lc-jian-ji-shuang-zhi-zhen-ji-2eg8/
https://leetcode-cn.com/problems/remove-element/solution/shua-chuan-lc-shuang-bai-shuang-zhi-zhen-mzt8/