435. Non-overlapping Intervals
题目链接:435. Non-overlapping Intervals
思路链接:代码随想录贪心算法-无重叠区间
思路
Runtime:O(nlogn)
空间复杂度:O(n)
方法一:如果做过之前的打重叠气球那题,这道题就能秒了。思路是差不多的,无非就是需要再初始化一个result来记录需要减去几个区间,只要一发现重复区间就result++,直到遍历结束。
局部最优:一次性尽可能找出与当前遍历区间重合的所有区间
全局最优:找到所有重合的区间
方法二:
局部最优:优先选右边界小的区间,所以从左向右遍历,留给下一个区间的空间大一些,从而尽量避免交叉。
全局最优:选取最多的非交叉区间。
将数组按照右边界大小从小到大排列,然后从左向右遍历,找最小右区间,用最小右区间与左区间的大小判断是否重合,如果不重合,就记录下来。最后将所有区间的个数与记录下来的值相减就可。
Code
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
// 方法一
// 先按照左区间从小到大排列,如果左区间相等,就按照右区间从小到大排列
Arrays.sort(intervals, (a, b) -> {
if (a[0] == b[0]) {
return a[1] - b[1];
} else {
return a[0] - b[0];
}
});
// 初始化需要减去的区间个数
int result = 0;
// 初始化最小右边界
int rightMinLine = intervals[0][1];
// 从第二个区间开始遍历
for (int i = 1; i < intervals.length; i++) {
if (rightMinLine > intervals[i][0]) {
rightMinLine = Math.min(rightMinLine, intervals[i][1]);
result++;
} else {
rightMinLine = intervals[i][1];
}
}
return result;
}
}
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
// 方法二
// 按照右区间从小到大排列,如果右区间相等,就按照左区间从小到大排列
Arrays.sort(intervals, (a, b) -> {
if (a[1] == b[1]) {
return a[0] - b[0];
} else {
return a[1] - b[1];
}
});
// 初始化nonOverlap
int nonOverlap = 1;
// 初始化最小右区间
int maxRightLine = intervals[0][1];
// 从左到右遍历
for (int i = 1; i < intervals.length; i++) {
// 比较最小右区间与遍历区间的左区间,判断是否重合,如果不重合,就更新nonOverlap
if (maxRightLine <= intervals[i][0]) {
maxRightLine = intervals[i][1];
nonOverlap++;
}
}
return intervals.length - nonOverlap;
}
}
763. Partition Labels
题目链接:763. Partition Labels
思路链接:代码随想录贪心算法-划分字母区间
思路
这道题看了题解才做出来。
没有十分明显的贪心算法的感觉。首先就是统计出字符串内所有字母出现的最大位置的下标。然后就是遍历字符串,记录之前遍历过的字符最大的出现下标,如果当前遍历到的字符下标与记录的之前遍历过字符的最大下标相等,那么就要分割。
还有一种用重叠区间的做法,感觉太复杂了。
Code
import java.util.List;
import java.util.LinkedList;
class Solution {
public List<Integer> partitionLabels(String s) {
// 初始化result列表
List<Integer> result = new LinkedList<>();
// 初始化字母出现最远位置的数组
int[] letters = new int[27];
// 记录每个字母的最远位置
for (int i = 0; i < s.length(); i++) {
letters[s.charAt(i) - 'a' + 1] = i;
}
// 初始化切割区间的最左侧与最右侧的index
int max = 0;
int min = 0;
// 遍历字符串,如果当前的字符的位置与字母出现最远位置相等,那么就得到分割线
for (int i = 0; i < s.length(); i++) {
// System.out.print(letters[s.charAt(i) - 'a' + 1] + " ");
// System.out.println("");
// System.out.print(i + " ");
// 更新字符出现的最远下标
max = Math.max(max, letters[s.charAt(i) - 'a' + 1]);
if (max == i) {
result.add(max - min + 1);
min = i + 1; // 更新最左侧下标
}
}
// System.out.println("");
// for (int i : letters) {
// System.out.print(i + " ");
// }
return result;
}
}
56. Merge Intervals
题目链接:56. Merge Intervals
思路链接:代码随想录贪心算法-合并区间
思路
这道题也秒了,就是对重叠区间的方法进行修改。
局部最优:每次合并都取最大的右边界,这样就可以合并更多的区间了
全局最优:合并所有重叠区间
现在不是找最小右区间了,而是要找最小左区间与最大右区间。
Code
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> result = new LinkedList<>();
// 按左边界从小到大排序,如果相等,按照右边界从小到大排序
Arrays.sort(intervals, (a, b) -> {
if (a[0] == b[0]) {
return a[1] - b[1];
} else {
return a[0] - b[0];
}
});
// 初始化最小左边界以及最大右边界
int left = intervals[0][0];
int right = intervals[0][1];
// 从左向右遍历
for (int i = 1; i < intervals.length; i++) {
// 如果重合,就更新最大右边界
if (right >= intervals[i][0]) {
right = Math.max(right, intervals[i][1]);
} else {
// 如果不重合,就把合并区间加入result,然后改变最小左边界以及最小右边界到当前遍历的区间
result.add(new int[]{left, right});
left = intervals[i][0];
right = intervals[i][1];
}
}
// 将最后一个没加的区间加上
result.add(new int[]{left, right});
return result.toArray(new int[result.size()][]);
}
}