代码随想录算法训练营第三期day37-贪心算法06

目录

1. T738:单调递增的数字

2. T714:买卖股票的最佳时机含手续费【动规暂搁】

2.1 思路

2.2 代码实现

3. T968:监控二叉树

思路

3.1 思路

3.1.1 二叉树遍历

3.1.2 处理逻辑

3.2 代码实现


1. T738:单调递增的数字

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

给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。

提示:

  • 0 <= n <= 109

S:这题开始不再排序~

我自己一度有2个疑问 -> 悟了:

  1. 当需要 -- 的时候,由于只 -- 1次,所以前一位 -- 完以后也并不能保证 -- 完之后会局部单调递增;

    • 而是要在后一位赋值为9以后才能实现局部单调递增;

    • 实际上 -- 为的,除了下一次的比较,主要是最后一次 --,也就是首位数字 --

  2. 如果当前要 -- 的位置数值已经是0,再 -- 会怎么样?(本题中实际上不可能发生,纯粹就是好奇)

C++:

    int monotoneIncreasingDigits(int n) {
        if (n <= 9) return n;
        string numStr = to_string(n);
        // int flag = 10;
        // flag用来标记赋值9从哪里开始
        // 设置为这个默认值,为了防止第二个for循环在flag没有被赋值的情况下执行
        int flag = numStr.size();
        for (int i = numStr.size() - 1; i > 0; --i) {
            if (numStr[i - 1] > numStr[i]) {
                flag = i;
                numStr[i - 1]--;// 1、为的是最后一下;2、好奇:如果是0?
            }
        }
        for (int i = numStr.size() - 1; i >= flag; --i) {
            numStr[i] = '9';
        }
        return stoi(numStr);
    }

Java:

    public int monotoneIncreasingDigits(int n) {
        char[] numStr = String.valueOf(n).toCharArray();
        int flag = numStr.length;
        for (int i = numStr.length - 1; i > 0; --i) {
            if (numStr[i - 1] > numStr[i]) {
                flag = i;
                numStr[i - 1]--;
            }
        }
        for (int i = numStr.length - 1; i >= flag; --i) {
            numStr[i] = '9';
        }
        return Integer.parseInt(String.valueOf(numStr));
    }

2. T714:买卖股票的最佳时机含手续费【动规暂搁】

T714:给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

提示:

  • 1 <= prices.length <= 5 * 104

  • 1 <= prices[i] < 5 * 104

  • 0 <= fee < 5 * 104

S:跟前面的T122:买卖股票的最佳时机Ⅱ相比,本题的唯一不同就在于每次买卖都要交手续费,也就是说如果赚的钱还交不起手续费,那就别卖,或者干脆刚开始就别买(这样更好)。

比如下例,在8卖6买,或者一直到9才卖都是一样的收益为6,区别仅在于交了几次手续费

    prices : [1,3,2,8,6,9],fee = 2

2.1 思路

如果使用贪心策略,就是最低值买,最高值(如果算上手续费还盈利)就卖。

此时无非就是要找到两个点:买入日期和卖出日期。

  • 买入日期:其实很好想,遇到更低点就记录一下。

  • 卖出日期:这个就不好算了,但也没有必要算出准确的卖出日期,只要当前价格大于(最低价格+手续费),就可以收获利润,至于准确的卖出日期,就是连续收获利润区间里的最后一天(并不需要计算是具体哪一天)。

在做收获利润操作的时候其实有三种情况:

  • 情况一:收获利润的这一天,并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以,后面要继续收获利润。

  • 情况二:前一天是收获利润区间里的最后一天(相当于真正的卖出了),今天要重新记录最小价格了。

  • 情况三:不作操作,保持原有状态(买入,卖出,不买不卖)

贪心算法的思路有一点一定要想明白:
  • 在遍历的过程中,会不断地更新股价最低点、不断累加利润,而累加利润 != 售出变现,这是两码事!!!
    • 也就是说,只有在进入下一次循环(除非当前已经是最后一轮循环)之后,最低股价得到更新(降低),这才是真正的售出并变现!否则就是(买了的话)继续持有,或者(没买的话)继续观望。

2.2 代码实现

C++:

    int maxProfit(vector& prices, int fee) {
        int res = 0;
        int minPrice = INT_MAX;
        for (int i = 0; i < prices.size(); ++i) {
            // 情况二:结合情况一的操作,就可以相当于买入
            minPrice = min(prices[i], minPrice);
            // 情况三:保持原有状态
            if (prices[i] > minPrice && prices[i] <= minPrice + fee) {
                continue;// 已经买了就继续拿着,没买就别买,因为此时买则不便宜,卖则亏本
            }
            // 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出(如果下次循环没有符合情况二,就当这次没卖)
            if (prices[i] > minPrice + fee) {
                res += prices[i] - fee - minPrice;
                minPrice = prices[i] - fee;// 情况一:核心理解行
            }
        }
        return res;
    }

Java:

    public int maxProfit(int[] prices, int fee) {
        if (prices.length == 1) return 0;
        int res = 0;
        int minPrice = prices[0];
        for (int i = 1; i < prices.length; ++i) {
            minPrice = prices[i] < minPrice ? prices[i] : minPrice;
            if (prices[i] > minPrice + fee) {
                res += prices[i] - fee - minPrice;
                minPrice = prices[i] - fee;
            }
        }
        return res;
    }

3. T968:监控二叉树【难】

T968:给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

提示:

  1. 给定树的节点数的范围是 [1, 1000]。

  2. 每个节点的值都是 0。

S:有一阵没有这么懵了,说实话就算看完题解并且理解,还是自认光凭自己想不到这份上。。。

思路

3.1 思路

3.1.1 二叉树遍历

首先,要遍历二叉树,而且是从叶子节点开始遍历,因为头结点放不放摄像头也就省下一个摄像头,而叶子节点放不放摄像头省下了的摄像头数量是指数阶别的。

    所以我们要从下往上看,

  • 局部最优:让叶子节点的父节点安摄像头,所用摄像头最少,

  • 整体最优:全部摄像头数量所用最少!

而从下往上遍历,自然是用后序遍历~

3.1.2 处理逻辑

每个节点自身可能有如下三种状态:

  • 该节点无覆盖

  • 本节点有摄像头

  • 本节点有覆盖

分别用三个数字来表示:

  • 0:该节点无覆盖

  • 1:本节点有摄像头

  • 2:本节点有覆盖

其实我个人更喜欢这样表示【见Java版】:

  • 0:该节点无覆盖

  • 1:本节点有覆盖

  • 2:本节点有摄像头

每个结点可能符合如下四类情况之一:

  • 情况1:左右节点都有覆盖

左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了。【从下往上】

  • 情况2:左右节点至少有一个无覆盖的情况

如果是以下情况,则中间节点(父节点)应该放摄像头:

    • left == 0 && right == 0 左右节点无覆盖;

    • left == 1 && right == 0 左节点有摄像头,右节点无覆盖;

    • left == 0 && right == 1 左节点有无覆盖,右节点摄像头;

    • left == 0 && right == 2 左节点无覆盖,右节点覆盖;

    • left == 2 && right == 0 左节点覆盖,右节点无覆盖

  • 情况3:左右节点至少有一个有摄像头

如果是以下情况,其实就是 左右孩子节点有一个有摄像头了,那么其父节点应该是2(覆盖状态)

    • left == 1 && right == 2 左节点有摄像头,右节点有覆盖;

    • left == 2 && right == 1 左节点有覆盖,右节点有摄像头;

    • left == 1 && right == 1 左右节点都有摄像头

  • 情况4:头结点没有覆盖

以上都处理完了,递归结束之后,还有一种可能,就是头结点还处于无覆盖的情况

3.2 代码实现

C++:

    int minCameraCover(TreeNode* root) {
       res = 0;
       if (!traversal(root)) ++res;// 情况四
       return res;
    }
private:
    int res;
    int traversal(TreeNode* node) {
        if (!node) return 2;// 空节点,该节点有覆盖
        int left = traversal(node->left);
        int right = traversal(node->right);
        if (left == 2 && right == 2) {// 情况一
            return 0;
        } else if (!left || !right) {// 情况二
            ++res;
            return 1;
        } else if (left == 1 || right == 1) {// 情况三
            return 2;
        }
        // 这个 return -1 逻辑不会走到这里。
        return -1;
    }

Java:用了自己修改过的节点状态定义方式,其实还是一样的

    private int res = 0;
    public int minCameraCover(TreeNode root) {
        if (traversal(root) == 0) ++res; 
        return res;
    }
    private int traversal(TreeNode node) {
        if (node == null) return 1;
        int left = traversal(node.left);
        int right = traversal(node.right);
        if (left == 1 && right == 1) {
            return 0;
        } else if (left == 0 || right == 0) {
            ++res;
            return 2;
        } else if (left == 2 || right == 2) {
            return 1;
        }
        return -1;
    }

你可能感兴趣的:(贪心算法,算法,c++,java,leetcode)