算法训练1-day33-动态规划

31 阅读3分钟
  1. 198. 打家劫舍

和斐波那契数列,还有跳楼梯很类似,都是根据前面的两项变化

AC代码:

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 1) {
            return nums[0];
        }
        int n = nums.size();
        // dp数组定义:第i间房能获取的最大利润
        vector<int> dp(n + 1);
        dp[0] = nums[0];
        dp[1] = max(dp[0], nums[1]);
        for (int i = 2; i < n; ++i) {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
        }

        return dp[n - 1];
    }
};
  1. 213. 打家劫舍 II

不能打劫相邻的屋子且所有屋子构成一个环,意味着第0位的屋子和最后一位的屋子相邻; 我们可以将问题分成两份:从第0位的屋子开始和从第1位的屋子开始,这样问题就又变为和198. 打家劫舍想同的问题了,只是从第0位开始的那一份结束在第n-2位的屋子,而从第1位开始的那一份结束于第n-1位的屋子,这样分我们就不用再处理环了

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n == 1) {
            return nums[0];
        } else if (n == 2) {
            return max(nums[0], nums[1]);
        }
        vector<int> beginFromZero(n);
        vector<int> beginFromOne(n);
        beginFromZero[0] = nums[0];
        beginFromZero[1] = max(beginFromZero[0], nums[1]);
        for (int i = 2; i < n - 1; ++i) {
            beginFromZero[i] =
                max(beginFromZero[i - 1], beginFromZero[i - 2] + nums[i]);
        }

        beginFromOne[1] = nums[1];
        beginFromOne[2] = max(beginFromOne[1], nums[2]);
        for (int i = 3; i < n; ++i) {
            beginFromOne[i] =
                max(beginFromOne[i - 1], beginFromOne[i - 2] + nums[i]);
        }

        return max(beginFromZero[n - 2], beginFromOne[n - 1]);
    }
};
  1. 337. 打家劫舍 III

递归+记忆化搜索:每个节点都有两种状态:打劫或是不打劫;如果打劫当前节点,那么要求前一个节点不能被打劫,且后一个节点也不能被打劫了;如果不打劫当前节点,那么前一个节点没有要求,后一个节点既可以打劫也可以不打劫。 用两个哈希表分别记录每个节点打劫或不打劫所能得到的最大金额

树形dp:用一个vector来保存状态,从底到顶返回,避免每种状态都去递归计算一次

代码如下:

class Solution {
public:
    unordered_map<TreeNode*, int> robOnNode;
    unordered_map<TreeNode*, int> noRobOnNode;
    int rob(TreeNode* root) {
        if (root == nullptr) {
            return 0;
        }
        return max(GetAnswer(root, true), GetAnswer(root, false));
    }
    int GetAnswer(TreeNode* root, bool canRob) {
        if (root == nullptr) {
            return 0;
        }
        int sum_rob = 0;
        int sum_no_rob = 0;

        if (canRob) {
            if (robOnNode.find(root) == robOnNode.end()) {
                sum_rob += root->val;
                sum_rob += GetAnswer(root->left, false);
                sum_rob += GetAnswer(root->right, false);
                robOnNode.emplace(root, sum_rob);
            } else {
                sum_rob = robOnNode[root];
            }
        }

        if (noRobOnNode.find(root) == noRobOnNode.end()) {
            sum_no_rob += GetAnswer(root->left, true);
            sum_no_rob += GetAnswer(root->right, true);
            noRobOnNode.emplace(root, sum_no_rob);
        } else {
            sum_no_rob = noRobOnNode[root];
        }

        return max(sum_no_rob, sum_rob);
    }
};

//树形dp
class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> result = robTree(root);
        return max(result[0], result[1]);
    }
    // 长度为2的数组,0:不偷,1:偷
    vector<int> robTree(TreeNode* cur) {
        if (cur == NULL) return vector<int>{0, 0};
        vector<int> left = robTree(cur->left);
        vector<int> right = robTree(cur->right);
        // 偷cur,那么就不能偷左右节点。
        int val1 = cur->val + left[0] + right[0];
        // 不偷cur,那么可以偷也可以不偷左右节点,则取较大的情况
        int val2 = max(left[0], left[1]) + max(right[0], right[1]);
        return {val2, val1};
    }
};