【算法】贪心算法:LeetCode 56 合并区间、LeetCode 738 单调递增的数字

LeetCode 56 合并区间

(中等)

题目

描述
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

示例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] 可被视为重叠区间。

思路

此题的题型也是属于两种判断因素的贪心算法题(类似题目可参考本系列之前文章),在本题中,这两种判断因素也就是指各区间的起始下标,终止下标,因此我们先确定下其中一个,这里我是按先确定下起始坐标来做的,也就是先将各区间按起始坐标从小到大排序,从而使得起始坐标可以有序地确定。之后遍历这个存放区间的二维数组,再建立一个用于存储结果的二维数组,例如,名为 result,可先将第一个区间数组放入 result,这样就不用单独处理 result 为空时的情况了(当然也可以不单独处理,为空时和不重叠时在此题中正好是一种情况),在循环遍历时,如果 result 的最后一个区间与当前区间不可重叠合并,那么就把当前区间加入到 result 末尾,如果可重叠合并,那么把 result 的最后一个区间的终止下标,更新为它自身与 当前区间终止下标中较大的那个值即可,这样就能尽可能得让这个区间向后覆盖更大的范围,从而完成当前及后续正确的合并。

实现

public class TX15合并区间 {
    public int[][] merge(int[][] intervals) {
        if (intervals == null || intervals.length == 0) {
            return null;
        }

        int[][] result = new int[intervals.length][2];
        int index = 0;

        int[][] copy = new int[intervals.length][intervals[0].length];
        for (int i = 0; i < copy.length; i++) {
            copy[i] = Arrays.copyOf(intervals[i], intervals[0].length);
        }

        Arrays.sort(copy, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] - o2[0];
            }
        });

        for (int i = 0; i < copy.length; i++) {
            if (index == 0 || result[index - 1][1] < copy[i][0]) { //第一次加 或 result的最后一个区间与当前区间不可重叠合并
                result[index++] = new int[]{copy[i][0], copy[i][1]};
            } else { //可重叠合并
                int tmpIndex = index - 1; //result中最后一个有效元素的下标
                result[tmpIndex][1] = Math.max(result[tmpIndex][1], copy[i][1]);
            }
        }

        return Arrays.copyOf(result, index);
    }

    public static void main(String[] args) {
        TX15合并区间 s = new TX15合并区间();
        System.out.println("s.merge(new int[][]{{1, 3}, {2, 6}, {8, 10}, {15, 18}}) = " +
                Arrays.deepToString(s.merge(new int[][]{{1, 3}, {2, 6}, {8, 10}, {15, 18}})));


        System.out.println("s.merge(new int[][]{{{1, 4}, {4, 5}}) = " +
                Arrays.deepToString(s.merge(new int[][]{{1, 4}, {4, 5}})));
    }
}

在这里插入图片描述

在这里插入图片描述

LeetCode 738 单调递增的数字

(中等)

题目

描述
给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。

(当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。)

示例1
输入: N = 10
输出: 9

示例 2:
输入: N = 1234
输出: 1234

示例 3:
输入: N = 332
输出: 299

思路

本题很容易就可以用暴力解法来实现,但是由于这是一道中档题,所有肯定会在时间上卡要求,不然真就是一道入门级别的题目,也就没有写下此题解的必要了。

超时的暴力解法:(不出意外,肯定超时,大家可忽略这种写法

public class TX16单调递增的数字 {
    public int monotoneIncreasingDigits(int n) {
        for (int i = n; i >= 0; i--) {
            if (accept(i)) {
                return i;
            }
        }
        return 0;
    }

    public boolean accept(int n) {
        String numStr = String.valueOf(n);

        for (int i = 0; i < numStr.length() - 1; i++) {
            if (numStr.charAt(i) > numStr.charAt(i + 1)) {
                return false;
            }
        }

        return true;
    }

    public static void main(String[] args) {
        TX16单调递增的数字 s = new TX16单调递增的数字();
        System.out.println("s.monotoneIncreasingDigits(10) = " + s.monotoneIncreasingDigits(10));
        System.out.println("s.monotoneIncreasingDigits(1234) = " + s.monotoneIncreasingDigits(1234));
        System.out.println("s.monotoneIncreasingDigits(332) = " + s.monotoneIncreasingDigits(332));
    }
}

在这里插入图片描述
在这里插入图片描述
因此,此题也还是要用贪心的思路来解决,要保证各个位数上的数字是单调递增的,并且可以允许相等的情况,因此此题将此特性描述为非递减比较好,这其中的核心思想就是,当一个数中,高位比低位的数字大时,可将高位 - 1,将低位置为最大的数字 9,因此,我们可以遍历这个数字的每一个位置,来进行这样的操作,但是,这其中也有两点问题需要注意

  • 不要从左到右遍历:从左到右遍历会使得如果较低位出现了较小的数,那么之前的高位就无法再更新,例如,像是 332 、341这样的数,因此,要从右到左进行遍历,因为高位我们可以最终将其置为 9(前提是如果不符合)
  • 不要将高位-1,低位置 9 的操作放在一次遍历中: 这样会让较低位数字符合要求,但是较高位不符合要求的数字无法更新其较低位数字为最大值 9,例如 100,如果放在一次遍历进行上述两个操作,最终结果为 90,而不是正确值 99,像这样的还有 9234,会得出 8934,因此,一旦高位数字已经减一了,那么低位自然就可以置为最大数字 9,这是理所应当的,因此这样的操作应该单独放在一个遍历中完成,才能不会和高位-1操作冲突。

实现

public class TX16单调递增的数字 {
    public int monotoneIncreasingDigits(int n) {
        StringBuilder numStr = new StringBuilder(String.valueOf(n));

        int index = numStr.length(); //记录最后一次-1的数字的位置

        for (int i = numStr.length() - 1; i > 0; i--) {
            if (numStr.charAt(i - 1) > numStr.charAt(i)) { //前一个数大于后一个数:前一个数-1,后一个数置9
                index = i;
                numStr.setCharAt(i - 1, (char) (numStr.charAt(i - 1) - 1));
            }
        }

        //因为index前一位已经-1了,因此index及其之后的数都可以置为最大的数字——9
        for (int i = index; i < numStr.length(); i++) {
            numStr.setCharAt(i, '9');
        }

        return Integer.parseInt(numStr.toString());
    }

    public static void main(String[] args) {
        TX16单调递增的数字 s = new TX16单调递增的数字();
        System.out.println("s.monotoneIncreasingDigits(10) = " + s.monotoneIncreasingDigits(10));
        System.out.println("s.monotoneIncreasingDigits(1234) = " + s.monotoneIncreasingDigits(1234));
        System.out.println("s.monotoneIncreasingDigits(332) = " + s.monotoneIncreasingDigits(332));
        System.out.println("s.monotoneIncreasingDigits(100) = " + s.monotoneIncreasingDigits(100));
    }
}

在这里插入图片描述

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超周到的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值