刷题计划——动态规划dynamic programming(二)

1339.分裂二叉树的最大乘积

题目:

给你一棵二叉树,它的根为 root 。请你删除 1 条边,使二叉树分裂成两棵子树,且它们子树和的乘积尽可能大。

由于答案可能会很大,请你将结果对 10^9 + 7 取模后再返回。

实例一

示例 1:

输入:root = [1,2,3,4,5,6]
输出:110
解释:删除红色的边,得到 2 棵子树,和分别为 1110 。它们的乘积是 11011*10
示例 2:
输入:root = [1,null,2,3,4,null,null,5,6]
输出:90
解释:移除红色的边,得到 2 棵子树,和分别是 156 。它们的乘积为 9015*6)

示例 3:
输入:root = [2,3,9,10,7,8,6,5,4,11,1]
输出:1025

示例 4:
输入:root = [1,1]
输出:1

分析题目之后,要得到分裂二叉树的最大乘积,必须遍历所有遍历所有节点。而分裂之后的二叉树,必定是原二叉树的子树,这就将问题化为求二叉树的两个子树的最大乘积

遍历二叉树的四种遍历方法中,先遍历子树再遍历根节点的算法只有后序遍历,因此使用后序遍历来遍历该二叉树,并将各子树的和存储在一个数组res中。

在求得各子树的和之后,将所有可能的两棵子树和与根节点的值相乘取最大值。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<long> sums;
    static const int mod = 1e9 + 7;
    int maxProduct(TreeNode* root) {
        postOrder(root);
        long res = 0;
        for (int i = 0; i < sums.size() - 1; ++i) {
            //  The last num of array sums is the sum of whole tree.
            //  sum.back()-sum[i]  means the another part of the separated tree by deleting side-i
            res = max(res, sums[i] * (sums.back() - sums[i]));
        }
        return (int)(res % mod);
    }

    long postOrder(TreeNode* root) {
        if(!root){
            return 0;
        }
        long res = root->val + postOrder(root->left) + postOrder(root->right);      //  The Sum of value in the child tree
        sums.push_back(res);        //  restore the Sum
        return res;
    }
};

买卖股票的最佳时机(简单)

题目:

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。


示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0

两次遍历法,指针法等都可以解决这道题,但如何使用动态规划解决类似的股票问题,参考一个方法团灭 6 道股票问题 写下这道题解。

动态规划法的核心是求出状态转移方程以及边界问题,但这两个问题都是那么容易解决。在一拿到这种题目时,将其化简为求后序数字对之前数字的最大差值,如何将每个数字的状态存储对应和表示就成了第一个要解决的问题。

每一个日期有两个选择,买或者卖,用一个大小为2的数组表示,下标为[0]表示买,下标为[1]表示卖,而总计有N个日期,因此使用二维数组就为 dp[N][2] ,其中N-1的下标表示第N-1天,[N-1][0] 表示最后一天购买股票所得到的收益,[N-1][1] 表示最后一天卖出股票所得到的收益。很明显,最后一天买入的股票不会带来任何收入,因此返回结果时只需考虑最后一天卖出的股票收益

这样就可以得到状态方程:
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]);
dp[i][1] = max(dp[i-1][1], -princes[i]);

i == 0时会发生边界问题,因此提前判断边界条件:
if i == 0dp[i][0] = 0, dp[i][0] = -princes[i];

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty()){
            return 0;
        }
        vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
        for(int i = 0; i < prices.size(); ++i){
            if(!i){
            	//	i == 0
                dp[i][0] = 0;
                dp[i][1] = -prices[i];
                continue;
            }
            //  Action sell means gaining money, for which, the best way for transaction is 
            //  comparing the income yesterday and today, then select the max income as 
            //  today's income.
            //  dp[i-1][1] means the stock bought in yesterday, it's throwing money away,
            //  dp[i-1][1]+prices[i] means the income we can gain if the stock is sold in terday. 
            dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]);
            //	Action buy means throwing away the profit we own now
            dp[i][1] = max(dp[i-1][1], -prices[i]);
        }
        return dp[prices.size()-1][0];
    }
};

1277.统计全为 1 的正方形子矩阵(中等)

题目:

给你一个 m * n 的矩阵,矩阵中的元素不是 0 就是 1,请你统计并返回其中完全由 1 组成的 正方形 子矩阵的个数。

示例 1:
输入:matrix =
[
  [0,1,1,1],
  [1,1,1,1],
  [0,1,1,1]
]
输出:15
解释: 
边长为 1 的正方形有 10 个。
边长为 2 的正方形有 4 个。
边长为 3 的正方形有 1 个。
正方形的总数 = 10 + 4 + 1 = 15.

示例 2:
输入:matrix = 
[
  [1,0,1],
  [1,1,0],
  [1,1,0]
]
输出:7
解释:
边长为 1 的正方形有 6 个。 
边长为 2 的正方形有 1 个。
正方形的总数 = 6 + 1 = 7.
class Solution {
public:
    int countSquares(vector<vector<int>>& matrix) {
        if(matrix.empty()){
            return 0;
        }
        int row = matrix.size(), column = matrix.back().size();
        vector<vector<int>> dp(row, vector<int>(column, 0));
        int numOfSquare = 0;
        for(int i = 0; i < row; i++){
            for(int j = 0; j < column; j++){
                if(i == 0 || j == 0){
                    dp[i][j] = matrix[i][j];
                }
                else if(matrix[i][j] == 0){
                    dp[i][j] = 0;
                }
                else{
                    dp[i][j] = min(min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1;
                }
                numOfSquare += dp[i][j];
            }
        }

        return numOfSquare;
    }
};

221.最大正方形

题目:

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

示例:
输入: 

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

输出: 4

暴力法已不再多提,
当从左上角的 [0][0] 开始遍历整个矩阵,若在一次遍历内解决问题则必须记录此前的状态。通过对比下标 [2][2] 与下标 [2][3] 元素,可以化简为使用对角线右下角元素来判断正方形的面积

使用动态规划的步骤:

  • 将问题划分为子问题求解; 若要求的最大的正方形,则需求的各小正方形中的最大一个
  • 设立状态转移方程;
    dp(i, j)=min(dp(i−1, j), dp(i−1, j−1), dp(i, j−1))+1
  • 边界条件;数组遍历结束。

很明显,判断下标为(i, j)的元素的正方形大小时需要得到 (i-1, j) , (i-1, j-1), (i, j-1)的正方形面积, 因此将 dp数组row + 1, column + 1;

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if(matrix.empty()){
            return 0;
        }
        int row = matrix.size(), column = matrix.back().size();
        vector<vector<int>> dp(row+1, vector<int>(column+1, 0));
        int maxSide = 0;
        for(int i = 1; i <= row; i++){
            for(int j = 1; j <= column; j++){
                if(matrix[i-1][j-1] == '1'){
                //	matrix[i-1][k-1] is '1', the request to constructing a square is settled.
                    dp[i][j] = min(min(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1;
                    //	Get the max side of square.
                    maxSide = max(maxSide, dp[i][j]);
                }
            }
        }
        //	Area
        return maxSide*maxSide;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值