题目与题解
435. 无重叠区间
题目链接:435. 无重叠区间
代码随想录题解:435. 无重叠区间
解题思路:
求无重叠区间,要返回需要移除区间的最小数量,使剩余区间互不重叠,相当于有区间重叠时,只能保留其中一个区间,且为了保证最后能保留的区间尽可能多,保留的区间要尽可能小。
首先将左边界作为第一关键字,对区间进行升序排序,这样保证左边界都是有序的,只需要判断前一个保留的右边界与下一个左边界是否重合。如果有重合,则需要移除的区间数目加一,右边界更新为当前区间和右边界之间的较小值。否则右边界更新为当前区间右边界。
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length <= 1) return 0;
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
if (o1[0] == o2[0])
return Integer.compare(o1[1], o2[1]);
return Integer.compare(o1[0], o2[0]);
}
});
int end = intervals[0][1];
int count = 0;
for (int i = 1; i < intervals.length; i++) {
if (end > intervals[i][0]) {
count++;
end = Math.min(intervals[i][1], end);
} else {
end = intervals[i][1];
}
}
return count;
}
}
看完代码随想录之后的想法
随想录的方法一反其道而行之,先对区间右边界进行排序,求非重合区间的个数,再用总区间数减掉它,得到最终需要移除区间的个数。说实话感觉有点难想有点绕,还是喜欢方法二这种从区间左边界开始排序,直接求需要移除区间的办法。
遇到的困难
一开始想的太粗暴了,遍历时只要有重合就直接移除当前区间,忽略了有时比起前一个区间,当前区间才是最优解(如前一个区间是[1,4],当前区间是[2,3],肯定保留更小的当前区间更优)。还有就是要注意end是需要实时更新的,否则一定出错。
763.划分字母区间
题目链接:763.划分字母区间
代码随想录题解:763.划分字母区间
解题思路:
先不提算法,假设手动计算区间划分,做法应该是:首先记录第一个字母第一次出现的位置和最后一次出现的位置,然后再从第一个字母最后一次出现的位置出发,记录第二个字母最后一次出现的位置,然后取这两个最后一次出现位置的较大值,更新本次分割的终点,然后再对第三个、第四个。。。直到本次终点的字母做相同的操作,就得到了一个分割序列长度;下一个序列从上次分割终点后的第一个字母开始,重复上述操作。
写成代码就是:设置每次分割的起点start和终点end,初始化为0,对start和end之间的字符进行遍历。每轮遍历先记录当前字符,然后搜索end+1到字符串结束中间有没有当前字符,如果有,更新end为当前字符最后一次出现的位置。每轮遍历完成后,记录start到end之间的字符数,然后更新start = end = end + 1,开始下一次的遍历。
为了节约时间,每轮遍历之前还需要设置一个大小为26的charExistence的boolean数组,用来记录当前字符有没有被查找过,如果查找过就可以跳过本轮循环。
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer> result = new ArrayList<>();
int start = 0;
int end = 0;
while (start < s.length() && end < s.length()) {
boolean[] charExistence = new boolean[26];
for (int i = start; i <= end ; i++) {
char c = s.charAt(i);
if (charExistence[c - 'a']) continue;
charExistence[c - 'a'] = true;
for (int j = end + 1; j < s.length(); j++) {
if (s.charAt(j) == c) end = j;
}
}
result.add(end - start + 1);
start = end + 1;
end = start;
}
return result;
}
}
看完代码随想录之后的想法
随想录的解答更巧妙,先统计每个字符最后一次出现的位置,然后再遍历字符串,更新分割区间的右边界,直到当前字符最后一次出现的位置跟右边界一致,说明当前分割完成,记录字符数即可。
class Solution {
public List<Integer> partitionLabels(String S) {
List<Integer> list = new LinkedList<>();
int[] edge = new int[26];
char[] chars = S.toCharArray();
for (int i = 0; i < chars.length; i++) {
edge[chars[i] - 'a'] = i;
}
int idx = 0;
int last = -1;
for (int i = 0; i < chars.length; i++) {
idx = Math.max(idx,edge[chars[i] - 'a']);
if (i == idx) {
list.add(i - last);
last = i;
}
}
return list;
}
}
遇到的困难
一开始直接求解会有点无从下手,可以分步做,先写出第一个字符怎么操作,第二个字符怎么操作,再填充遍历的起点终点和其他细节,就可以写出完整的过程了。
56. 合并区间
题目链接:56. 合并区间
代码随想录题解:56. 合并区间
解题思路:
这题跟去除重复区间的思想有点类似,不同的地方在于遇到重叠的区间时,要取两个区间中更大的右边界,来合并两个区间。
首先对区间进行排序,关键字为区间左边界。然后用result这一链表记录合并后的区间值,初始化时将第一个区间加入。遍历剩余区间,如果当前区间的左边界大于result最后一个元素的右边界,说明二者不重叠,不用合并,将当前区间加入result即可;否则将result的最后一个元素右边界更新为更大的那个右边界。
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals.length <= 1) return intervals;
Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0]));
LinkedList<int[]> result = new LinkedList<>();
result.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
if (result.getLast()[1] >= intervals[i][0]) {
int[] interval = result.pollLast();
result.add(new int[]{interval[0], Math.max(interval[1], intervals[i][1])});
} else {
result.add(intervals[i]);
}
}
return result.toArray(new int[result.size()][]);
}
}
看完代码随想录之后的想法
思路差不多,写法上稍微有点区别,就不赘述了。
遇到的困难
这题有前面几题的铺垫,写起来非常容易了。
今日收获
集中练习了一下区间重叠相关的题目,这些题重点在于:如何对原区间进行排序,排序后再遍历时,如何根据要求更新区间的边界值,保证不重复不遗漏。