第八章 贪心算法 part05
- 435. 无重叠区间
- 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。问题就是要求非交叉区间的最大个数当确定区间1和区间2重叠后,如何确定是否与区间3也重叠呢,就是取区间1和区间2有边界的最小值,因为这个最小值之前的部分一定是区间1和区间2的重合部分,如果这个最小值也触达到区间3,那么说明区间123都是重合的。
- 接下来就是找大于区间1结束位置的区间,是从区间4开始,为什么不从区间5开始,别玩那个了这已经是按照有边界排序的了。区间4结束之后,再找到区间6,所以一共记录非交叉区间的个数是三个。总共区间个数是6,减去非交叉区间个数3,移除区间的最小数量就是3.
-
class Solution { public int eraseOverlapIntervals(int[][] intervals) { Arrays.sort(intervals, (a,b)-> { return Integer.compare(a[0],b[0]); }); int count = 1; for(int i = 1;i < intervals.length;i++){ if(intervals[i][0] < intervals[i-1][1]){ intervals[i][1] = Math.min(intervals[i - 1][1], intervals[i][1]); continue; }else{ count++; } } return intervals.length - count; } }
- 763.划分字母区间
- 字符串s由小写字母组成我们把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
- 思路:一想到分割字符串就想到了回溯,但本题其实不用回溯去暴力搜索。题目要求同一字母最多出现在一个片段中。那么如何把同一个字母的都圈在一个区间呢
- 如果没有接触过这种题目的话,还挺有难度的。在遍历的过程中相当于是找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。此时前面出现过所有字母,最远也就到这个边界了。
- 可以分为一下两步:
- 统计每个字符最后出现的位置
- 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等,则找到了分割点
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; } }
这段代码是一个循环,用于遍历字符数组
chars
,并将每个字符在edge
数组中对应的位置(通过字符减去'a'
的ASCII码值得到的索引)更新为当前字符在字符串S
中最后一次出现的索引。以下是一个举例说明:
假设输入的字符串
S
为 "abacd",那么chars
数组的内容为['a', 'b', 'a', 'c', 'd']
。初始时,
edge
数组的所有元素都是0。 -
当
i
为0时,表示遍历到chars
数组的第一个字符'a'
,则将edge['a' - 'a']
更新为0,即edge[0] = 0
。 -
当
i
为1时,表示遍历到chars
数组的第二个字符'b'
,则将edge['b' - 'a']
更新为1,即edge[1] = 1
。 -
最终,
edge
数组的内容为[2, 1, 3, 4, 0, 0, ..., 0]
,表示每个字符在字符串S
中最后一次出现的索引。 -
当
i
为2时,表示遍历到chars
数组的第三个字符'a'
,则将edge['a' - 'a']
更新为2,即edge[0] = 2
。 -
当
i
为3时,表示遍历到chars
数组的第四个字符'c'
,则将edge['c' - 'a']
更新为3,即edge[2] = 3
。 -
当
i
为4时,表示遍历到chars
数组的第五个字符'd'
,则将edge['d' - 'a']
更新为4,即edge[3] = 4
。 -
// 创建一个长度为26的整型数组,用于记录每个字符在字符串S中最后一次出现的索引 int[] edge = new int[26]; // 将字符串S转换为字符数组 char[] chars = S.toCharArray(); // 遍历字符数组,记录每个字符在字符串S中最后一次出现的索引 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. 合并区间
- 给出一个区间的集合,请合并所有重叠的区间,本题的本质其实还是判断重叠区间的问题,这几道题都是判断区间重叠色问题,区别就是判断区间重叠后的逻辑判断,本题是判断区间重叠后要进行区间合并。所以一样的套路,先排序,让所有的相邻区间尽可能地重叠在一起,按左边界或有边界排序都可以,处理逻辑稍有不同。按照左边界从小到大拍好序之后,如果有一定的重叠,本题相邻区间也算重叠,所以是<=
- 知道如何判断重复之后,剩下的就是合并了。如何去模拟合并区间呢?其实就是用合并区间后左边界和有边界,作为一个新的区间,加入到result数组里就可以了。如果没有合并就把元区间加入到result数组。
-
class Solution { public int[][] merge(int[][] intervals) { List<int[]> res = new LinkedList<>(); //按照左边界排序 Arrays.sort(intervals, (x, y) -> Integer.compare(x[0], y[0])); //initial start 是最小左边界 int start = intervals[0][0]; int rightmostRightBound = intervals[0][1]; for (int i = 1; i < intervals.length; i++) { //如果左边界大于最大右边界 if (intervals[i][0] > rightmostRightBound) { //加入区间 并且更新start res.add(new int[]{start, rightmostRightBound}); start = intervals[i][0]; rightmostRightBound = intervals[i][1]; } else { //更新最大右边界 rightmostRightBound = Math.max(rightmostRightBound, intervals[i][1]); } } res.add(new int[]{start, rightmostRightBound}); return res.toArray(new int[res.size()][]); } } }