贪心算法第六篇:单调递增的数字 + 划分字母区间 + 合并线段区间

156 阅读4分钟

文章目录

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
    }
}