区间问题
本题在55. 跳跃游戏的基础上又增加了一个”跳跃次数最少“的限制。使得本题在跳跃游戏的基础上难度增加了不少。
跳跃游戏只需要一个接一个的顺序遍历,只要能遍历到最后一个就可以返回。
但是本题需要时刻记录着当前跳跃可以覆盖的区间里,下一次可以覆盖到的最右边的区间的最值。
在逻辑判断上增加了不少东西。我们来举个例子
nums = [2,3,1,1,4]
的答案应该是2。我们来模拟下跳跃的过程。
所以我们发现在一次跳跃的过程中。我们需要时刻更新记录着下一次跳跃能够覆盖的最大索引值。
结束条件:
-
下一次跳跃的最大索引下标 >= 数组的最大索引:
nextMaxIndex >= nums.length-1
-
本次跳跃到了最数组的最大索引:
curMaxIndex == nums.length-1
class Solution { public int jump(int[] nums) { int curMaxIndex = 0;//当前可以覆盖的的最远下标 int nextMaxIndex = 0;//下一步可以覆盖的最远下标 int res = 0; for (int i = 0; i < nums.length; i++) { nextMaxIndex = Math.max(i+nums[i], nextMaxIndex); //遍历到当前覆盖的最远下标 if(i == curMaxIndex) { //还没有走到头 if(i < nums.length-1) { curMaxIndex = nextMaxIndex; res++; //如果下一次跳跃可以直接跳到最后 if (nextMaxIndex >= nums.length-1) break;; }else break;//如果本次跳跃到了数组的最大索引 } } return res; } }
本题从逻辑上来判断其实是比较容易的,用贪心法的局部最优的思想:尽可能的打多重叠的部分。
那怎么用代码实现呢,代码中有一个比较巧妙的部分:更新最小右边界
步骤:
-
对数组的第一个维度升序排序
-
按照第一个维度对数组循环遍历
-
进行逻辑判断
-
如果当前气球的左边界 > 上一个气球的右边界,说明不重叠,
res++
(注意:res要从1开始,因为气球的个数肯定大于1) -
否则,把当前气球的右边界更新为
Math.min(points[i][1], points[i-1][1]);
,即上一个气球的右边界和本气球的右边界的最小值。
-
-
循环结束,得出res结果
public int findMinArrowShots(int[][] points) { //排序,从小到大排 Arrays.sort(points, (a, b) -> { return a[0] - b[0]; }); int res = 1; //和前一个的最小右边界对比 for (int i = 1; i < points.length; i++) { //不重合:左边界大于上一个的右边界 if (points[i][0] > points[i-1][1]) res++; else {//重合:更新最小右边界 points[i][1] = Math.min(points[i][1], points[i-1][1]); } } return res; } }
但是以上代码还有个小问题,[[-2147483646,-2147483645],[2147483646,2147483647]],用lambda表达式会导致这个例子输出异常。所以要改为Integer自带的compare方法进行判断。
class Solution { public int findMinArrowShots(int[][] points) { //排序,从小到大排 //有可能会溢出[[-2147483646,-2147483645],[2147483646,2147483647]] Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0])); int res = 1; //和前一个的最小右边界对比 for (int i = 1; i < points.length; i++) { //不重合:左边界大于上一个的右边界 if (points[i][0] > points[i-1][1]) res++; else {//重合:更新最小右边界 points[i][1] = Math.min(points[i][1], points[i-1][1]); } } return res; } }
和452. 用最少数量的箭引爆气球的思路是一模一样的,只不过把逻辑判断反过来就好。
class Solution { public int eraseOverlapIntervals(int[][] intervals) { Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); int res = 0; //和前一个的最小右边界对比 for (int i = 1; i < intervals.length; i++) { //重合:更新最小右边界 if (intervals[i][0] < intervals[i-1][1]){ intervals[i][1] = Math.min(intervals[i][1], intervals[i-1][1]); res++; } } return res; } }
和452. 用最少数量的箭引爆气球的思路是一模一样的,只不过把逻辑判断反过来就好。
class Solution { public int eraseOverlapIntervals(int[][] intervals) { Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); int res = 0; //和前一个的最小右边界对比 for (int i = 1; i < intervals.length; i++) { //重合:更新最小右边界 if (intervals[i][0] < intervals[i-1][1]){ intervals[i][1] = Math.min(intervals[i][1], intervals[i-1][1]); res++; } } return res; } }
本题属于是逻辑和代码都不是很好写的那种类型了。
首先我们先从逻辑出发,需要找出符合题目要求的子串。可以理解为:
-
找到每个字母的最远边界
-
该字母前的所有字母也都是已经到达最远边界
比如子串ababcbaca
其中a,b,c三个字母的最远边界分别为8,5,7。
在最后一个字母a之前出现过的所有字母(a,b)都已经到达最远边界。
所以我们首先需要一个数组来装每个字母的最远边界
int[] edge = new int[26]; char[] chars = s.toCharArray(); //存储每个字母的最后一次出现所在的索引 for (int i = 0; i < chars.length; i++) { int index = chars[i] - 'a'; edge[index] = i; }
其次,要遍历chars数组,看符合条件的位置。
int start = 0;//每个子串的起始索引 int idx = 0;//当前子串的最后一位索引 for (int i = 0; i < chars.length; i++) { int index = chars[i] - 'a'; idx = Math.max(idx, edge[index]);//记录当前出现过的字母的最大索引位置 if (idx == i) { res.add(i-start+1); start = i+1; } }
记录当前出现过的字母的最大索引位置,相对来说比较难理解,建议多看看,找个例子推一遍。
class Solution { public List<Integer> partitionLabels(String s) { List<Integer> res = new ArrayList<>(); int[] edge = new int[26]; char[] chars = s.toCharArray(); //存储每个字母的最后一次出现所在的索引 for (int i = 0; i < chars.length; i++) { int index = chars[i] - 'a'; edge[index] = i; } int start = 0;//每个子串的起始索引 int idx = 0;//当前子串的最后一位索引 for (int i = 0; i < chars.length; i++) { int index = chars[i] - 'a'; idx = Math.max(idx, edge[index]); if (idx == i) { res.add(i-start+1); start = i+1; } } return res; } }
本题课可以按照435. 无重叠区间和452. 用最少数量的箭引爆气球的思路来写,但是过程就会麻烦很多。
本题从逻辑上来说,可以说是很简单了,重合了就给合并了。代码上和前几题基本一致,就是有一些细节上的东西需要一点点的改。
class Solution { public int[][] merge(int[][] intervals) { //偶排序 Arrays.sort(intervals, (a,b) -> { if (a[0] == b[0]) return a[1] - b[1]; return a[0] - b[0]; }); //因为第二维度是固定为2,而第一维度是会随着重合区间越多而变少的,所以写成这种类型 List<int[]> res = new ArrayList<>(); int start = intervals[0][0];//区间的起始位置 for (int i = 1; i < intervals.length; i++) { //不重叠,把前面重叠的区间合成一个大区间加入结果集 if (intervals[i][0] > intervals[i-1][1]) { int[] interval = new int[]{start, intervals[i-1][1]}; res.add(interval); start = intervals[i][0];//设置下一个区间的起点 }else { intervals[i][1] = Math.max(intervals[i-1][1], intervals[i][1]); } } res.add(new int[]{start,intervals[intervals.length-1][1]}); return res.toArray(new int[res.size()][]); } }