Day37【贪心算法】738.单调递增的数字、968.监控二叉树

738.单调递增的数字

力扣题目链接/文章讲解

视频讲解

贪心思路:从个位向前看,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先让strNum[i - 1]--,然后strNum[i]及其之后的数字变为9

我们从后向前遍历数字,按照思路进行处理 

class Solution {
public:
    int monotoneIncreasingDigits(int N) {
        string strNum = to_string(N);
        // flag用来标记赋值9从哪里开始
        // 设置为这个默认值,为了防止第二个for循环在flag没有被赋值的情况下执行
        int flag = strNum.size();
        for (int i = strNum.size() - 1; i > 0; i--) {
            if (strNum[i - 1] > strNum[i] ) {
                flag = i;    // 位置flag及其之后的数字变为9
                strNum[i - 1]--;
            }
        }
        for (int i = flag; i < strNum.size(); i++) {
            strNum[i] = '9';
        }
        return stoi(strNum);
    }
};

注意 to_string 和 stoi 的运用,需要#include 

968.监控二叉树 

力扣题目链接/文章讲解

视频讲解

看到二叉树,我们总会想到递归三部曲或回溯

本题就用递归三部曲吧,需要结合贪心的思想

首先需要明确递归函数的功能: 递归函数功能是传入一个树的节点返回其应该被置为的状态

这里所谓的节点的状态有三种

  • 节点安装了摄像头
  • 节点无摄像头但被别的摄像头覆盖
  • 节点无摄像头且未被别的摄像头覆盖

我们分别有三个数字来表示:

  • 0:该节点无覆盖
  • 1:该节点有摄像头
  • 2:该节点无摄像头但有被覆盖

然后进入三部曲 

1、确定参数和返回值

根据递归函数作用得知:参数为一个节点,返回值为 int 表示其应该被置的状态

int traversal(TreeNode* cur)

2、确定终止条件

遇到空节点,应该返回其应该被置为的状态。那么问题来了,空节点究竟是哪一种状态呢? 空节点表示无覆盖? 表示有摄像头?还是有覆盖呢?

回归我们的贪心思想:为了让摄像头数量最少,我们要从叶子节点往根节点逐步设置节点状态,尽量让叶子节点的父节点安装摄像头,这样才能摄像头的数量最少。那么空节点不能是有摄像头的状态,这样叶子节就没有必要用摄像头去覆盖了,而是可以把摄像头放在叶子节点的爷爷节点上,会发生漏覆盖;空节点也不能是未覆盖的状态,这样叶子节点就要放摄像头了,这样摄像头可能会放多了

Day37【贪心算法】738.单调递增的数字、968.监控二叉树_第1张图片

所以空节点的状态只能是有覆盖,这样就可以在叶子节点的父节点放摄像头了

// 空节点,该节点有覆盖
if (cur == NULL) return 2;

3、确定单层递归的逻辑 

我们从叶子节点往上依次设置状态。当前节点如何被设置取决于其左右子节点被设置为的状态左右子节点的状态设置情况可以通过调用递归函数本身获取

代码如下,包含了三种情况的设置 

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
private:
    int result; // 统计摄像头个数
    int traversal(TreeNode* cur) {
        //0:该节点无覆盖
        //1:本节点有摄像头
        //2:本节点有覆盖

        // 空节点,该节点有覆盖
        if (cur == NULL) return 2;

        int left = traversal(cur->left);    // 获取左节点状态
        int right = traversal(cur->right);  // 获取右节点状态

        // 情况1
        // 左右节点都有覆盖
        if (left == 2 && right == 2) return 0;

        // 情况2
        // left == 0 && right == 0 左右节点无覆盖
        // left == 1 && right == 0 左节点有摄像头,右节点无覆盖
        // left == 0 && right == 1 左节点有无覆盖,右节点摄像头
        // left == 0 && right == 2 左节点无覆盖,右节点覆盖
        // left == 2 && right == 0 左节点覆盖,右节点无覆盖
        if (left == 0 || right == 0) {
            result++;    // 需要添加摄像头
            return 1;
        }

        // 情况3
        // left == 1 && right == 2 左节点有摄像头,右节点有覆盖
        // left == 2 && right == 1 左节点有覆盖,右节点有摄像头
        // left == 1 && right == 1 左右节点都有摄像头
        // 其他情况前段代码均已覆盖
        if (left == 1 || right == 1) return 2;

        // 以上代码我没有使用else,主要是为了把各个分支条件展现出来,这样代码有助于读者理解
        // 这个 return -1 逻辑不会走到这里。
        return -1;
    }

public:
    int minCameraCover(TreeNode* root) {
        result = 0;

        if (traversal(root) == 0) { // root 无覆盖,需要在root的地方加一个摄像头
            result++;
        }
        return result;
    }
};

需要注意,root 如果最后被置的状态为0,表明节点 root 还未被覆盖,需要在 root 处增加一个摄像头 


回顾总结 

贪心算法章节告一段落

贪心算法确实没啥规律,唯一稍微有点儿规律的就是重叠区间问题中的排序、遍历、判断重叠。当然只是相对有规律,具体的处理过程还是要看题目描述的,也具有很大差异性

另外提一下,很多贪心算法都需要排序 

你可能感兴趣的:(代码随想录,贪心算法,算法,leetcode,数据结构,c++)