Day31–贪心–56. 合并区间,738. 单调递增的数字,968. 监控二叉树
本文精华在《968. 监控二叉树》的讲解,非常生动易懂。
56. 合并区间
方法:贪心
思路:
- Arrays.srot(),使用Lambda表达式指定规则。(使用Integer.compare(x, y)安全比较,避免溢出)
- 《重叠区间》问题的常规套路
- 用Stream将二维列表转换为二维数组
class Solution {
public int[][] merge(int[][] intervals) {
// 结果集
List<List<Integer>> res = new ArrayList<>();
// 按左边界从小到大排序(使用Integer.compare(x, y)安全比较,避免溢出)
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
// 初始化左右边界为第一个区间
int left = intervals[0][0];
int right = intervals[0][1];
// 从第二个区间开始遍历(与前一个区间的关系)
for (int i = 1; i < intervals.length; i++) {
// 重叠了,更新右边界,右边界为max(前一区间右边界,当前区间右边界)
// 就是要找到最长的“尾巴”,才能把这两个区间“包进去”
if (right >= intervals[i][0]) {
right = Math.max(right, intervals[i][1]);
} else {
// 不重叠,把前面的区间放到结果集
List<Integer> list = new ArrayList<>();
list.add(left);
list.add(right);
res.add(list);
// 更新左右边界为当前的区间
left = intervals[i][0];
right = intervals[i][1];
}
}
// 处理最后一个区间
List<Integer> list = new ArrayList<>();
list.add(left);
list.add(right);
res.add(list);
// 将二维列表转成二维数组返回
return res.stream()
.map(innerList -> innerList.stream()
.mapToInt(Integer::intValue)
.toArray())
.toArray(int[][]::new);
}
}
738. 单调递增的数字
方法:贪心
思路:
- 如果不单调递增,找到要“减一”的那一位,后面的全部变9。
- 注意处理相同的数的情况。比如332,要减一的位置不是第二个3,而是第一个3。
- 注意注意处理首位是0的情况
// 如果不合适,找到要“减一”的那一位,后面的全部变9
class Solution {
public int monotoneIncreasingDigits(int n) {
// 转换成数组
String s = "" + n;
int[] arr = new int[s.length()];
for (int i = 0; i < s.length(); i++) {
arr[i] = s.charAt(i) - '0';
}
// 开始从前向后遍历,两位一组看,当不符合单调递增的时候退出循环
int i = 1;
for (; i < arr.length; i++) {
if (arr[i - 1] > arr[i]) {
i = i - 1;
break;
}
}
// 非正常退出,证明break了
if (i != arr.length) {
// 往前找到要“减一”的位置。如果是严格大于的话,不会进这个循环。
// 这个while处理相同的数的情况。比如332,要减一的位置不是第二个3,而是第一个3
while (i >= 1 && arr[i - 1] == arr[i]) {
i--;
}
// 当前位置减一,变成232
arr[i]--;
// 后一位开始,到末尾,全部变成9.也就是299
i++;
while (i < arr.length) {
arr[i++] = 9;
}
}
// 数组求和,注意处理首位是0的情况
int j = 0;
if (arr[0] == 0) {
j = 1;
}
int sum = 0;
for (; j < arr.length; j++) {
sum = sum * 10 + arr[j];
}
return sum;
}
}
思路:
- 从右向左遍历,就简单多了。找到第一个需要“减一”的地方,后续全部变成9
- 注意,这里parseInt()方法, 自动处理了前导零的情情况。比如"09"输出9
// 从右向左遍历,就简单多了
class Solution {
public int monotoneIncreasingDigits(int n) {
String s = String.valueOf(n);
char[] ch = s.toCharArray();
int start = s.length();
for (int i = s.length() - 2; i >= 0; i--) {
if (ch[i] > ch[i + 1]) {
ch[i]--;
start = i + 1;
}
}
for (int i = start; i < s.length(); i++) {
ch[i] = '9';
}
// 注意,这里parseInt()方法, 自动处理了前导零的情情况。比如"09"输出9
return Integer.parseInt(String.valueOf(ch));
}
}
968. 监控二叉树
方法:贪心
思路:
- 后续遍历,往上传递信息:
- 三个状态,1,可以提供帮助,0不需要管我,-1,需要帮助
- if的顺序不能变。要优先考虑儿子的情况,再到自己。
- 最后别忘了处理根的情况,当“根”说自己想要被老爸管,它是没老爸的,要自己装
最能体现贪心的地方是:“都不需要管,我也不管自己了,找老爸管”
// 两个状态用boolean,三个状态要用int
class Solution {
// 计数
int count = 0;
public int minCameraCover(TreeNode root) {
int condition = postorderTravel(root);
// 最后别忘了处理根的情况,当“根”说自己想要被老爸管,它是没老爸的,要自己装
if (condition == -1) {
count++;
}
return count;
}
// 三个状态,1,可以提供帮助,0不需要管我,-1,需要帮助
private int postorderTravel(TreeNode node) {
// 后续遍历左右根
int left = 0;
int right = 0;
// 左
if (node.left != null) {
left = postorderTravel(node.left);
}
// 右
if (node.right != null) {
right = postorderTravel(node.right);
}
// 根,开始处理情况
// 有一方需要管,也要照顾儿子,装灯
if (left == -1 || right == -1) {
count++;
return 1;
}
if (left == 0 && right == 0) {
// 都不需要管,我也不管自己了,找老爸管
return -1;
}
// 有儿子管我了,不需要自己装,也不需要老爸装。
if (left == 1 || right == 1) {
return 0;
}
// 注意,上面三个if的顺序不能变。要优先考虑儿子的情况,再到自己。
return 0;
}
}
贪心总结
很多贪心题,做完了之后根本没发现自己用了贪心的方法。可能我本来就足够贪心吧。哈哈哈。
再来念一遍贪心的思路:找出局部最优,并可以推出全局最优。
题目分类:
- 两个维度维权问题
- 分发糖果
- 根据身高重建队列
- 重叠区间问题
- 跳跃游戏
- 引爆气球
- 无重叠区间
- 划分字母区间
- 合并区间
- 其他
- 加油站
- 摆动序列
- 最大子数组和
- 监控二叉树