经典动态规划:打家劫舍系列问题

打家劫舍系列总共有三道,难度设计非常合理,层层递进。第一道是比较标准的动态规划问题,而第二道融入了环形数组的条件,第三道更绝,让盗贼在二叉树上打劫.
House Robber | 经典动态规划:打家劫舍系列问题_第1张图片

public int rob(int[] nums);

题目很容易理解,而且动态规划的特征很明显。解决动态规划问题就是找「状态」和「选择」。
假想你就是这个专业强盗,从左到右走过这一排房子,在每间房子前都有两种选择:抢或者不抢。
如果你抢了这间房子,那么你肯定不能抢相邻的下一间房子了,只能从下下间房子开始做选择。
如果你不抢这间房子,那么你可以走到下一间房子前,继续做选择。
当你走过了最后一间房子后,你就没得抢了,能抢到的钱显然是 0(base case)。
以上的逻辑很简单吧,其实已经明确了「状态」和「选择」:你面前房子的索引就是状态,抢和不抢就是选择。
状态表示:dp[i][0/1]表示第i个房子偷或不偷的最高金额
初始边界:dp[0][] = {0,0}
转移方程:
当前房子不偷:dp[i][1] = dp[i-1][0] + nums[i]
当前房子偷: dp[i][0] = max{dp[i-1][0],dp[i-1][1]}

int rob(vector<int>& nums) {
     
    vector<vector<int>> dp(nums.size()+1,vector<int>(2,0));
    for(int i = 1; i <= nums.size(); i++)
    {
     
        dp[i][0] = max(dp[i-1][1],dp[i-1][0]);
        dp[i][1] = dp[i-1][0] + nums[i-1];
    }
    return max(dp[nums.size()][0],dp[nums.size()][1]);
}

间复杂度:O(N)
空间复杂度:O(N)
优化:用四个变量表示代替上面的状态

int rob(vector<int>& nums) {
     
    int n = nums.size();
    int last_0 = 0, last_1 = 0, curr_0 = 0, curr_1 = 0;
    for(int i = 0; i < nums.size(); i++)
    {
     
        curr_0 = max(last_0, last_1);
        curr_1 = last_0 + nums[i];
        last_0 = curr_0;
        last_1 = curr_1;
    }
    return max(curr_0, curr_1);
}

时间复杂度:O(N)
空间复杂度:O(1)

打家劫舍II(动态规划)
经典动态规划:打家劫舍系列问题_第2张图片
这道题目和第一道描述基本一样,强盗依然不能抢劫相邻的房子,输入依然是一个数组,但是告诉你这些房子不是一排,而是围成了一个圈。

也就是说,现在第一间房子和最后一间房子也相当于是相邻的,不能同时抢。比如说输入数组 nums=[2,3,2],算法返回的结果应该是 3 而不是 4,因为开头和结尾不能同时被抢。
那么就会有这两种情况:
第一间房子抢的化最后一间房子就不能抢
最后一间房子抢的话第一间房子就不能抢
所以结合打家劫舍I我们可以实现这样种情况,然后返回这两种情况的最大值就是答案。

    int rob(vector<int>& nums) {
     
        if(nums.size() == 0) return 0;
        if(nums.size() == 1) return nums[0];
        // 第一间房子抢的化最后一间房子就不能抢
        vector<int> nums1(nums.begin()+1,nums.end());
        // 最后一间房子抢的话第一间房子就不能抢
        vector<int> nums2(nums.begin(),nums.end()-1);
        // 取两者最大值
        return max(rob2(nums1),rob2(nums2));
    }
    int rob2(vector<int>& nums) {
     
        int pre = 0,curr = 0;
        for(int i = 0; i < nums.size(); i++)
        {
     
            int tmp = curr;
            curr = max(curr,pre + nums[i]);
            pre = tmp;
        }
        return curr;
    }

打家劫舍III(树型DP)
经典动态规划:打家劫舍系列问题_第3张图片
状态转移方程:
当前房子偷 = 左房子不偷 + 右房子不偷 + 当前房子的钱
当前房子不偷 = max(左房子不偷,左房子偷) + max(右房子不偷,右房子偷)

函数maxPrifox:返回的是偷和不偷的最优策略对应的结果值

    struct data
    {
     
        int y; // 偷
        int n; // 不偷
    };
    int rob(TreeNode* root) {
     
        if(!root) return 0;
        data rs =  maxPrifox(root);
        return max(rs.y, rs.n);
    }
    data maxPrifox(TreeNode* root)
    {
     
        if(!root) return {
     0, 0};
        // 1. 拿到左房子的所有状态信息
        data le = maxPrifox(root->left);
        // 2. 拿到右房子的所有状态信息
        data ri = maxPrifox(root->right);
        data rs;
        // 3. 当前房子偷 = 左房子不偷 + 右房子不偷 + 当前房子的钱
        rs.y = le.n + ri.n + root->val;
        // 4. 当前房子不偷 = max(左房子不偷,左房子偷) + max(右房子不偷,右房子偷)
        rs.n = max(ri.y, ri.n) + max(le.y, le.n);
        return rs;
    }

你可能感兴趣的:(LeetCode)