【力扣一刷】代码随想录day37(贪心算法part6:738.单调递增的数字、968.监控二叉树、总结 )

本文介绍了如何使用两种方法处理单调递增数字问题,包括从左往右遍历和从右往左遍历,并详细阐述了如何在监控二叉树问题中应用贪心算法。同时提及了贪心算法在二维权衡问题和特定类型的题目中的应用.
摘要由CSDN通过智能技术生成

【738.单调递增的数字】中等题

方法一  从左往右遍历(自己做的,稍微抽象)

思路:

1、编写getIncreasing(int num, int idx)方法,用于根据处理后的非递增的开始索引,返回处理后的最大递增数。(注意:是在原方法中处理非递增的开始索引再通过形参传入)

  • 例如12351,非递增的开始是数字5,非递增的开始索引是3,那么就得到times为10,对12351先除10再乘10,就得到12350,再减1,就得到想要的结果12349。
  • 例如9999899,非递增的开始是第4个数字9,非递增的开始索引是3,但是因为非递增的开始数字是9,前面还是相同的数字9,不处理直接减就会得到9998999,不符合题意,因此要将非递增的开始索引向左移动到相同数字的开始索引,即0,则得到的times是1000000,返回的值为(9999899 / 1000000)* 1000000 - 1 = 8999999 

2、原方法的处理逻辑:

将数字转换成字符串,从左到右遍历,寻找非递增的开始索引。

  • 找到非递增的开始索引后,如果该索引需要处理,则将处理后的索引传入getIncreasing方法,如果不需要处理,直接往getIncreasing方法传入索引即可,getIncreasing方法返回的值就是结果。
  • 如果没有找到非递归索引,证明这个数本身就是单调递增的,直接返回原来的数即可。
class Solution {
    public int monotoneIncreasingDigits(int n) {
        String s = Integer.toString(n);
        for (int i = 0; i < s.length() - 1; i++){
            char c1 = s.charAt(i); // 当前遍历数字
            char c2 = s.charAt(i+1); // 下一个遍历的数字

            // 找到非递增的开始数字
            if (c1 - '0' > c2 - '0'){
                // 如果是9999899这种情况,遍历到8前面的9前面还是9,则进入下面的if分支,那么需要定位到第一个9
                if (i > 0 && c1 == s.charAt(i-1)) {
                    int idx = i;
                    while (idx > 0){
                        if (s.charAt(idx-1) == c1) idx--;
                        else break;
                    }
                    return getIncreasing(n, idx);
                }
                // 其余情况12351,定位到5,i为3,times为10,获取12350,再减1,就是答案12349
                return getIncreasing(n, i);
            }
        }
        return n; // 如果找到非递增的情况,证明都是递增的,直接返回原来的数字
    }

    // 根据非递增的开始索引,返回处理后的最大递增数
    public int getIncreasing(int num, int idx){
        int times = (int) Math.pow(10, Integer.toString(num).length() - 1 - idx);
        return (num / times) * times - 1;
    }
}
  • 时间复杂度: O(logn),数字n的位数是logn,遍历所有位数一次
  • 空间复杂度: O(logn),字符串的长度等于数字n的位数logn

方法二  从右往左遍历

思路:当前遍历的数字和前面的数字为非递增关系,则前面的数字-1,start标记要开始变成9的索引。

例子:332

从右往左遍历:

字符串为332,遍历 i = 2,32是非递增,则3-1=2,332变成322,start = 2

字符串为322,遍历 i = 1,32是非递增,则3-1=2,322变成222,start = 1

遍历结束,开始变9:

从start = 1开始的数字都是9,所以222变成299

返回结果299

易错点:不能遇到非递增就把当前数字变为9,把前面数字-1

例子:124428

如果按这个逻辑,遍历到2的时候,发现42不是递增,则变成124398,再遍历到3时,遇到43不是递增,则变成123998,后序没有非递增,则返回123998,明显结果是错的,对的结果是123999。

class Solution {
    public int monotoneIncreasingDigits(int n) {
        String s = Integer.toString(n);
        StringBuffer sb = new StringBuffer(s);
        int start = s.length();

        for (int i = sb.length() - 1; i > 0; i--){
            char cur = sb.charAt(i);
            char pre = sb.charAt(i - 1);
            // 遇到非递增,将前一个数字减1,更新开始变9的索引
            if (cur - 'a' < pre - 'a'){
                sb.setCharAt(i-1, (char) (pre - 1));
                start = i;
            }
        }
        // 从开始变9的索引遍历,将后面的都变成9
        for (int i = start; i < s.length(); i++){
            sb.setCharAt(i, '9');
        }
        return Integer.parseInt(sb.toString());
    }
}
  • 时间复杂度: O(logn),数字n的位数是logn,遍历所有位数一次
  • 空间复杂度: O(logn),字符串的长度等于数字n的位数logn

【968.监控二叉树】困难题

难点:需要考虑三种不同的状态

  • 状态0:未被覆盖(无摄像头)
  • 状态1:有摄像头
  • 状态2:已被覆盖(无摄像头)

(一开始很容易只考虑了两种状态,即有摄像头和无摄像头,造成题解无法通过)

思路:

1、利用后序递归遍历二叉树,根据子节点的状态确定当前节点的状态。子节点的状态和当前节点的状态关系如下(子节点状态无序):

  • (子节点状态, 子节点状态) -> 当前节点状态
  • (0, 0) -> 1
  • (0, 1) -> 1
  • (0, 2) -> 1   
  • (1, 1) -> 2
  • (1, 2) -> 2
  • (2, 2) -> 0

解释:

  • 只要有子节点有0,即存在未被覆盖的子节点,则当前节点必须装摄像头,即确定当前节点的状态是1。
  • 子节点没有0的情况下,只要有子节点有1,证明当前节点一定会被覆盖,可以不装摄像头,即确定当前节点的状态是2。
  • 如果子节点都是2,即都已被覆盖状态,而当前节点就无法被监测,贪心算法为了减少摄像头数,可以不在当前节点装摄像头,等上面的父节点装,即当前节点是状态0。

2、确定叶子节点和空节点的状态

  • 贪心算法想尽量减少摄像头的个数,则避免在叶子节点放置摄像头,以尽可能地扩大监测范围。因此,叶子节点不能是状态1,加上叶子节点可以让父节点放置摄像头进行检测,因此叶子节点应该设置为状态0。
  • 根据状态转换关系,只有两个都是状态2的子节点,当前节点才能为状态0,因此空节点应该设置为状态2。
class Solution {
    int num = 0;
    public int minCameraCover(TreeNode root) {
        // 如果只有一个节点,那么返回的状态为0,需要装1个摄像头
        if (dfs(root) == 0) num++; // 在判断的同时调用了dfs方法进行二叉树的后序递归遍历
        return num;
    }

    // 1、确定参数和返回值:传入节点,返回当前节点的状态值
    public int dfs(TreeNode root){
        // 2、确定终止条件
        // 如果是空节点,返回2
        if (root == null) return 2;

        // 3、确定单层递归逻辑
        int left = dfs(root.left);
        int right = dfs(root.right);

        if (left == 0 || right == 0) { // (0, 0) (0, 1) (0, 2)
            num++;
            return 1;
        }
        else if (left == 2 && right == 2) return 0;  // (2, 2) - 叶子节点
        else return 2; // (1, 1) (1, 2)
    }
}
  • 时间复杂度: O(n),n为节点个数,相当于遍历一次所有节点
  • 空间复杂度: O(n),递归栈的深度最坏为n

【贪心算法总结】

1、本质:

局部最优 -> 全局最优

2、需要重点掌握的三类题型:

  • 二维权衡问题
  • 股票问题
  • 重叠区间问题

(根据代码随想录的贪心算法总结篇对各类题型进行复习)

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值