贪心算法第六篇:单调递增的数字 + 划分字母区间 + 合并线段区间
Posted Java架构师(公众号:毛奇志)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了贪心算法第六篇:单调递增的数字 + 划分字母区间 + 合并线段区间相关的知识,希望对你有一定的参考价值。
738.单调递增的数字
给定一个非负整数 N,找出小于或等于 N 的最大的整数(贪心要求),同时这个整数需要满足其各个位数上的数字是单调递增(硬性要求)。
(当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。)
示例 1:
输入: N = 10
输出: 9
示例 2:
输入: N = 1234
输出: 1234
示例 3:
输入: N = 332
输出: 299
说明: N 是在 [0, 10^9] 范围内的一个整数。
贪心解法
题目要求小于等于N的最大单调递增的整数,那么拿一个两位的数字来举例。
例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]–,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。
这一点如果想清楚了,这道题就好办了。
局部最优:遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]–,然后strNum[i]给为9,可以保证这两位变成最大单调递增整数。
全局最优:得到小于等于N的最大单调递增的整数。
但这里局部最优推出全局最优,还需要其他条件,即遍历顺序,和标记从哪一位开始统一改成9。
此时是从前向后遍历还是从后向前遍历呢?
从前向后遍历的话,遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]减一,但此时如果strNum[i - 1]减一了,可能又小于strNum[i - 2]。
这么说有点抽象,举个例子,数字:332,从前向后遍历的话,那么就把变成了329,此时2又小于了第一位的3了,真正的结果应该是299。
所以从前后向遍历会改变已经遍历过的结果!
那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299
确定了遍历顺序之后,那么此时局部最优就可以推出全局,找不出反例,试试贪心。
class Solution {
public int monotoneIncreasingDigits(int N) {
char[] charArray = (N + "").toCharArray(); // int变为String
int flag = charArray.length; // 如果没有进入第一个循环的if块,第二个循环不需要执行
for (int i = charArray.length - 1; i >= 1; i--) { // 下面使用到了 i i-1,所以从[1,length-1]
if (charArray[i - 1] > charArray[i]) { // 硬性要求是小于或等于,大于就不行
flag = i; // 低位要全部变为9,不是仅这个i的位置要变为9
charArray[i - 1]--; // 高位要减少一
}
}
for (int i = flag; i < charArray.length; i++) {
charArray[i] = '9';
}
return Integer.parseInt(new String(charArray));
}
}
763.划分字母区间
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段(贪心目的),同一字母最多出现在一个片段中(硬性要求)。返回一个表示每个字符串片段的长度的列表。
示例:
输入:S = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释:划分结果为 “ababcbaca”, “defegde”, “hijhklij”。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 的划分是错误的,因为划分的片段数较少。
提示1:S的长度在[1, 500]之间。
提示2:S只包含小写字母 ‘a’ 到 ‘z’ 。
遍历的过程相当于是要找每一个字母的边界,「如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了」。此时前面出现过所有字母,最远也就到这个边界了。
可以分为如下两步:
1、统计每一个字符最后出现的位置;
2、从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点。
class Solution {
public List<Integer> partitionLabels(String S) {
List<Integer> result=new ArrayList<>(); // 里面存放长度
if (S==null || S.length()<=1) return result;
char[] charArray=S.toCharArray();
int[] hash=new int[27];
for (int i=0;i<S.length();i++)
hash[charArray[i]-'a']=i; // 如果一个字母在后面又出现了,后面的i会覆盖前面的,所以hash数组记录的就是每个字母最后出现的下标位置了
// 再遍历一次S字符串,因为要对其拆分
int right=0;
int left=0;
for (int i=0;i<S.length();i++){
right=Math.max(right,hash[charArray[i]-'a']); // 取一个比较大的
if (i == right){
// 找到位置了,就要拆分
result.add(right-left+1);
left=right+1; // 此时i==right 所以left=i+1也是一样的
}
}
return result;
}
}
56. 合并区间
给出一个区间的集合,请合并所有重叠的区间。
示例 1:
输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: intervals = [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
注意:输入类型已于2019年4月15日更改。请重置默认代码定义以获取新方法签名。
提示:intervals[i][0] <= intervals[i][1]
那么我按照左边界排序,
排序之后局部最优:每次合并都取最大的右边界,这样就可以合并更多的区间了,
整体最优:合并所有重叠的区间。
C++ push_back()函数 等价于list.add()
功能:函数将一个新的元素加到vector的最后面,位置为当前最后一个元素的下一个元素
push_back() 在Vector最后添加一个元素(参数为要插入的值)
C++ back()函数 等价于list.get(list.size()-1)
功能:返回当前vector容器中末尾元素的引用。
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals.length<=1) return intervals; // 长度为0或者长度为1,都可以直接返回
List<int[]> result=new ArrayList<>();
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0]; // 升序排列 相等的时候是否交换无所谓
}
});
boolean isFinished=false; // 没完成
for (int i=1;i<intervals.length;i++){
int start=intervals[i-1][0];
int end=intervals[i-1][1];
while (i<intervals.length && intervals[i][0] <= end){ // 等于也算重叠,也要合并
end = Math.max(end,intervals[i][1]); // 第一个元素和第二个元素取取出大的为end,然后i++,和第三个元素的[i][0]比较
if (i==intervals.length-1) isFinished=true;
i++;
}
result.add(new int[]{start,end});
}
if (!isFinished){
result.add(new int[]{intervals[intervals.length-1][0],intervals[intervals.length-1][1]});
}
return result.toArray(new int[result.size()][]); // 这里是result.size() 不是intervals.length
}
}
简化
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals.length<=1) return intervals; // 长度为0或者长度为1,都可以直接返回
List<int[]> result=new ArrayList<>();
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0]; // 升序排列 相等的时候是否交换无所谓
}
});
result.add(new int[]{intervals[0][0],intervals[0][1]}); // 第一个先放进去
for (int i=1;i<intervals.length;i++){
// int start=intervals[i-1][0];
// int end=intervals[i-1][1];
// while (i<intervals.length && intervals[i][0] <= end){ // 等于也算重叠,也要合并
// end = Math.max(end,intervals[i][1]); // 第一个元素和第二个元素取取出大的为end,然后i++,和第三个元素的[i][0]比较
// i++;
// }
// result.add(new int[]{start,end});
int[] lastElement = result.get(result.size()-1);
if (lastElement[1] >= intervals[i][0]){ // 大于和等于都算重叠,都要合并
// 重新设置这个元素的end list无法修改,只能先删除,后新增,删除前记得将需要的暂存下来
int start=lastElement[0];
result.remove(result.size()-1); // 删除最后一个元素,可以这样写
result.add(new int[]{start,Math.max(lastElement[1],intervals[i][1])}); // 直接赋值,不用Math.max了
}else{
result.add(intervals[i]);
}
}
return result.toArray(new int[result.size()][]); // 这里是result.size() 不是intervals.length
}
}
就是
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals.length<=1) return intervals; // 长度为0或者长度为1,都可以直接返回
List<int[]> result=new ArrayList<>();
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0]; // 升序排列 相等的时候是否交换无所谓
}
});
result.add(new int[]{intervals[0][0],intervals[0][1]}); // 第一个先放进去
for (int i=1;i<intervals.length;i++){
int[] lastElement = result.get(result.size()-1);
if (lastElement[1] >= intervals[i][0]){ // 大于和等于都算重叠,都要合并
// 重新设置这个元素的end list无法修改,只能先删除,后新增,删除前记得将需要的暂存下来
int start=lastElement[0];
result.remove(result.size()-1); // 删除最后一个元素,可以这样写
result.add(new int[]{start,Math.max(lastElement[1],intervals[i][1])}); // 直接赋值,不用Math.max了
}else{
result.add(intervals[i]);
}
}
return result.toArray(new int[result.size()][]); // 这里是result.size() 不是intervals.length
}
}
以上是关于贪心算法第六篇:单调递增的数字 + 划分字母区间 + 合并线段区间的主要内容,如果未能解决你的问题,请参考以下文章