#0
「快慢指针」下篇是对上篇的内容扩展进阶。在这里回顾一下双指针技巧的四个步骤
- 确定两个指针指向元素的含义
- 确定两个指针移动的条件
- 确定指针移动时的step
- 确定指针移动时的额外执行逻辑
上篇的几道题在这四个步骤中遇到的问题都很直观且简单,进阶的题目也绕不开这四个步骤,不过会在每个步骤的细节中加大难度。
下篇将会涉及到汇总区间和删除排序数组中的重复项 II。其中汇总区间加大的难点在第四步「确定指针移动时的额外执行逻辑」,相比上篇的几个问题,需要处理额外执行逻辑。删除排序数组中的重复项 II的难点在第二步「确定两个指针移动的条件」,由于题目描述中增加了更多的限制,因此指针移动的条件发生了变化。
#1
No.228 汇总区间
No.228问题的描述如下
给定一个无重复元素的有序整数数组 nums
。
返回 「恰好覆盖数组中所有数字」 的 「最小有序」 区间范围列表。也就是说,nums
的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums
的数字 x
。
列表中的每个区间范围 [a,b]
应该按如下格式输出:
"a->b"
,如果 a != b
"a"
,如果 a == b
「示例 1:」
❝输入:nums = [0,1,2,4,5,7]
输出:["0->2","4->5","7"]
解释:区间范围是:[0,2] --> "0->2" [4,5] --> "4->5" [7,7] --> "7"
❞
首先提取题干中的信息,数组是包含「无重复元素」的「有序整数数组」,并且没有原地输出的要求。面对这样的问题,不借助代码的做法会是这样:
从nums[i]开始->找到下一个不连续的数字nums[j]->转换区间范围的格式->将j赋值给i
到这里就能回答双指针技巧的四个步骤了
- 确定两个指针指向元素的含义
- fast指向当前构建区间范围之外的第一个数字
- slow指向当前构建区间范围的第一个数字
- 确定两个指针移动的条件
- 当nums[fast]==nums[fast - 1] + 1,slow停止,fast移动
- 当nums[fast]!=nums[fast - 1] + 1,fast停止,slow移动
- 确定指针移动时的step
- fast移动时的step是1
- slow移动至fast处
- 确定指针移动时的额外执行逻辑
- fast移动时没有额外执行
- slow移动时,以nums[slow]和nums[fast]构建区间范围
这道题的代码参考
class Solution {
public List summaryRanges(int[] nums) {
// 步骤1,确定两个指针指向元素的含义
int slow = 0, fast = 1, len = nums.length;
List res = new ArrayList<>();for (; fast // 步骤2、3,确定两个指针移动的条件及其移动时的stepwhile (fast 1] + 1) fast++;// 步骤4,执行额外逻辑
res.add(format(nums, slow, fast - 1));
slow = fast;
}if (slow res.add(format(nums, slow, fast - 1));return res;
}private String format(int[] nums,Integer start,Integer end) {if(start == end) return String.valueOf(nums[start]);return nums[start] + "->" + nums[end];
}
}
结合动图能更好的理解
![eae1734fcb77febeae7342a3e5e9ac07.gif](https://img-blog.csdnimg.cn/img_convert/eae1734fcb77febeae7342a3e5e9ac07.gif)
No.80 删除排序数组中的重复项 II
No.80问题的描述如下
给定一个增序排列数组 nums
,你需要在 「原地」 删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 「原地 修改输入数组」 并在使用 O(1) 额外空间的条件下完成。
「示例 1:」
❝输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。你不需要考虑数组中超出新长度后面的元素。
❞
这道题是「No26.删除排序数组中的重复项」进阶版,进阶的地方在于题目要求删除元素后使得每个元素最多出现两次,根据这个条件能够明确需要额外的计数器在执行双指针遍历的时候记录每个元素出现的次数。同样列举双指针技巧的四个步骤,看看这道题该如何解。
- 确定两个指针指向元素的含义
- fast指向原数组当中的元素
- slow指向"去重"后数字中最靠右侧的那一个
- 确定两个指针移动的条件
- fast随遍历进行移动,无条件
- 当nums[fast] != nums[slow]时,执行步骤
- 当nums[fast] == nums[slow]且count < 2时,执行步骤
- 确定指针移动时的step
- step是1
- 确定指针移动时的额外执行逻辑
- 当nums[fast] != nums[slow]时,nums[++slow] = nums[fast];count=1
- nums[fast] == nums[slow]且count < 2时,nums[++slow] = nums[fast]
这里的图示很清楚地说明了这两道题解法的差异所在
![41a48fec28e650abf1a4c9011d6fd7a3.png](https://img-blog.csdnimg.cn/img_convert/41a48fec28e650abf1a4c9011d6fd7a3.png)
代码参考
class Solution {
public int removeDuplicates(int[] nums) {
int n = nums.length;
// 初始化指针和计数器count
int slow = 0,fast = 1,count = 1;
for(;fast // fast随遍历进行移动
if (nums[fast] != nums[slow]) {
// 移动slow指针,并且重置计数器count
nums[++slow] = nums[fast];
count=1;
} else {
// 这里保证slow指向"去重"后数字中最靠右侧的那一个
if(count 2)
nums[++slow] = nums[fast];
// 记录元素重复的数量
count++;
}
}
return slow + 1;
}
}