代码随想录Day36-动态规划:力扣第337m、121e、122m、123h题

337m. 打家劫舍 III

题目链接
代码随想录文章讲解链接

方法一:记忆化递推

用时:18m22s

思路

采用后序遍历,因为要先知道两个子节点偷了多少钱,才能知道当前节点能偷多少钱。
对于以当前节点为根节点的二叉树,能偷的最高金额取决于两种情况:

  1. 偷当前节点,那么就不能偷子节点,要跳过子节点去考虑子节点的子节点。
  2. 不偷当前节点,那么就要考虑偷子节点。

能偷的最高金额为两者中较高者。由于情况1和情况2会重复计算子节点,所以可以用一个哈希表存储已经计算过的子节点的值。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
private:
    unordered_map<TreeNode*, int> hashMap;
public:
    int rob(TreeNode* root) {
        if (root == nullptr) return 0;
        if (root->left == nullptr && root->right == nullptr) return root->val;
        // 如果已经计算过当前节点,则直接从哈希表取值
        if (hashMap.find(root) != hashMap.end()) return hashMap[root];

        // 情况一:偷父节点
        int val1 = root->val;
        if (root->left) val1 += rob(root->left->left) + rob(root->left->right);
        if (root->right) val1 += rob(root->right->left) + rob(root->right->right);
        // 情况二:不偷父节点
        int val2 = 0;
        if (root->left) val2 += rob(root->left);
        if (root->right) val2 += rob(root->right);
        // 记录当前节点的值
        hashMap[root] = max(val1, val2);
        return hashMap[root];
    }
};

方法二:树形动态规划

用时:12m26s

思路

dp数组:dp[0] 表示偷当前节点能获得的金额,dp[1] 表示不偷当前节点能获得的金额
状态转移:通过递归函数的返回值来传递状态,采用后序遍历,因为要先知道子节点的状态,才能更新当前节点的状态

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
private:
    pair<int, int> dfs(TreeNode* node) {
        if (node == nullptr) return pair<int, int>(0, 0);
        pair<int, int> left = dfs(node->left);
        pair<int, int> right = dfs(node->right);
        // pair<偷当前节点的金额, 不偷当前节点的金额>
        return pair<int, int>(node->val + left.second + right.second, max(left.first, left.second) + max(right.first, right.second));
    }

public:
    int rob(TreeNode* root) {
        pair<int, int> p = dfs(root);
        return max(p.first, p.second);
    }
};

看完讲解的思考

无。

代码实现遇到的问题

无。


121e. 买卖股票的最佳时机

题目链接
代码随想录文章讲解链接

方法一:贪心

用时:7m33s

思路
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if (prices.size() < 2) return 0;
        int res = 0;
        int low = prices[0];
        for (int i = 1; i < prices.size(); ++i) {
            if (prices[i] <= low) low = prices[i];
            else res = max(prices[i] - low, res);
        }
        return res;
    }
};

方法二:动态规划

思路

dp数组:dp[i][0]表示第i天持有股票的最大收支,dp[i][1]表示第i天不持有股票的最大收支。
状态转移:

  1. dp[i][0]:第i天持有股票的最大收支可以分为两种情况:一是上一天持有股票,当天不卖掉;二是上一天不持有股票,当天买入。dp[i][0] = max(dp[i - 1][0], -prices[i])
  2. dp[i][1]:第i天不持有股票的最大收支也可以分为两种情况:一是上一天持有股票,当天卖掉;二是上一天不持有股票,当天也不买。dp[i][1] = max(dp[i - 1][0] + prices[i], dp[i - 1][1])

由于第i天的状态只跟前一天的有关,所以可以不用记录之前的状态,空间上可以优化,只用一个变量存储当前状态然后不断更新即可。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        pair<int, int> dp(-prices[0], 0);  // (持有股票的收支,不持有股票的收支)
        for (int i = 1; i < prices.size(); ++i) {
            // 当天不持有股票:1.上一天持有股票,当天卖掉;2.上一天不持有股票,当天也不买
            dp.second = max(dp.first + prices[i], dp.second);
            // 当天持有股票:1.上一天持有股票,当天不卖掉;2.上一天不持有股票,当天买入
            dp.first = max(dp.first, -prices[i]);
        }
        return dp.second;
    }
};

看完讲解的思考

感觉这个dp的解法本质上跟贪心是一致的。

代码实现遇到的问题

无。


122m. 买卖股票的最佳时机II

题目链接
代码随想录文章讲解链接

方法一:贪心

用时:2m13s

思路

局部最优:只要当天的股价高于前一天,那么就在前一天买入,在当天卖出。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int res = 0;
        for (int i = 1; i < prices.size(); ++i) {
            if (prices[i] > prices[i - 1]) res += prices[i] - prices[i - 1];
        }
        return res;
    }
};

方法二:动态规划

用时:19m31s

思路

跟上一题差不多,不同点在于,当天持有股票的情况,上一题由于只能买一次股票,所以若先前没有买股票,当天买的话,收支是为-prices[i]。而本题是可以多次买卖,所以当天买的话也得算上先前的收支,即dp1 - prices[i]。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int dp0 = -prices[0], dp1 = 0;
        for (int i = 1; i < prices.size(); ++i) {
            int newDp0 = max(dp0, dp1 - prices[i]);  // 当天持有股票
            dp1 = max(dp0 + prices[i], dp1);  // 当天不持有股票
            dp0 = newDp0;
        }
        return dp1;
    }
};

看完讲解的思考

这两题用贪心就很容易想,用dp感觉有点绕。

代码实现遇到的问题

无。


123h. 买卖股票的最佳时机III

题目链接
代码随想录文章讲解链接

方法一:动态规划

用时:58m3s

思路

dp数组:dp[i][0]表示第i天还未买过股票;dp[i][1]表示第i天第一次持有股票;dp[i][2]表示第i天已经卖出了1次股票;dp[i][3]表示第i天第二次持有股票;dp[i][4]表示第i天已经卖出了两次股票。
状态转移与121m题类似,只是多了几个状态。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<vector<int>> dp(prices.size(), vector<int>(5, 0));  // (未买过,第一次持有,第一次不持有,第二次持有,第二次不持有)
        dp[0][1] = -prices[0], dp[0][3] = -prices[0];
        for (int i = 1; i < prices.size(); ++i) {
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
            dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
            dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
            dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
        }
        return dp[prices.size() - 1][4];
    }
};

方法二:动态规划+滚动数组

用时:1m48s

思路
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<int> dp(5, 0);  // (未买过,第一次持有,第一次不持有,第二次持有,第二次不持有)
        dp[1] = -prices[0], dp[3] = -prices[0];
        for (int i = 1; i < prices.size(); ++i) {
            dp[4] = max(dp[4], dp[3] + prices[i]);
            dp[3] = max(dp[3], dp[2] - prices[i]);
            dp[2] = max(dp[2], dp[1] + prices[i]);
            dp[1] = max(dp[1], dp[0] - prices[i]);
        }
        return dp[4];
    }
};

看完讲解的思考

无。

代码实现遇到的问题

无。


最后的碎碎念

三天没刷题,搞比赛去了,手感掉了一些,还是得每天刷一刷,要是时间紧每天少刷点。

你可能感兴趣的:(代码随想录,动态规划,leetcode,算法,c++)