算法刷题打卡041 | 动态规划9-打家劫舍系列

Posted tsy_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法刷题打卡041 | 动态规划9-打家劫舍系列相关的知识,希望对你有一定的参考价值。

重温一下“打家劫舍三部曲”~

LeetCode 198 打家劫舍

题目链接:198. 打家劫舍 - 力扣(Leetcode)

 最简单的打家劫舍问题中,房屋的排列是一条直线,唯一的限制是不能“偷”连续两家,然后求能“偷”的最大结果。遍历房屋时,当前这家能不能“偷”,取决于前一家有没有被“偷”,当前这家nums[i]要偷时,状态从dp[i-2]推过来,加上当前的nums[i],不偷时状态直接从dp[i-1]推导,然后在两种可能的状态中取其中的最大值:

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n <= 2:
            return max(nums)
        dp0 = nums[0]
        dp1 = max(nums[0], nums[1])
        for i in range(2, n):
            # 当前这一家偷:dp0 + nums[i];不偷:维持前一家的最大金额,dp1
            dp = max(dp0 + nums[i], dp1)
            dp0 = dp1
            dp1 = dp
        return dp

LeetCode 213 打家劫舍II

题目链接:213. 打家劫舍 II - 力扣(Leetcode)

打家劫舍II相比原版,增加了房屋首尾相连起来的限制条件,使得偷不偷首尾房屋会成为一个困惑的点。一刷过代码随想录就知道,解题策略就是将这个环形拆解,因为首尾相连意味着nums[0]和nums[-1]必然只能“偷”一家,因此分别不考虑nums[0]、不考虑nums[1],就能用打家劫舍的线性方式求解,然后取二者的最大值:

class Solution:
    def rob_linear(self, nums1):
        if len(nums1) <= 2:
            return max(nums1)
        dp0, dp1 = nums1[0], max(nums1[0], nums1[1])
        for i in range(2, len(nums1)):
            dp = max(dp0+nums1[i], dp1)
            dp0, dp1 = dp1, dp
        return dp

    def rob(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return nums[0]
        # 将环形拆解为两段首尾不相连的线性:不包含尾元素、不包含首元素
        return max(self.rob_linear(nums[:-1]), self.rob_linear(nums[1:]))

这个解题思路确实很妙,不了解这个解题思路时,会想是不是需要以每个房屋为起点遍历一遍,找其中的最大值,这样的时间复杂度会是O(n^2)。而这里两轮的O(n)遍历就可以获得问题的解,并且已经囊括了可能的情况。

LeetCode 213 打家劫舍III

题目链接:337. 打家劫舍 III - 力扣(Leetcode)

升级版的打家劫舍开始在二叉树上进行了,此时一个房屋的相邻房屋包括它的左右子节点(如果有)以及它的父节点,并且入口在树的根节点。 看到二叉树很自然想到深度优先搜索遍历,虽然题目说入口是root处,但不代表一定要从root开始“偷”,因为小偷也做了“考察”。自己解题时考虑使用后序遍历,因为只有确定了左右子节点的状态才能考虑当前节点可能的状态。主要思路也记录在了代码注释中,在遍历时返回两个状态,分别表示偷当前节点时能获得的最大金额以及不偷当前节点时能获得的最大金额。当前节点只有偷和不偷两种状态,当前节点要偷时,左右子节点必然都不能偷,因此当前节点偷得的最大的金额只能是left_不偷+left_不偷+node.val;当前节点不偷时,左右子节点相互没有影响,偷和不偷都可以,取各自节点的最大值相加就能得到当前节点不偷时能获得的最大金额。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def rob(self, root: Optional[TreeNode]) -> int:
        def dfs(node):
            if not node:
                return [0, 0]  # 偷 不偷
            # 当前节点不偷时,左右子节点相互不相邻,取各自的最大值,记为LEFT、RIGHT,相加即可
            # 当left和right都没偷时,当前node才能偷,返回值为left_不偷 + right_不偷 + node.val
            # 因此当前节点不偷时的返回值为LEFT+RIGHT, 
            # 偷时为left_不偷+right_不偷+node.val
            left1, left2 = dfs(node.left)
            right1, right2 = dfs(node.right)
            return [left2 + right2 + node.val, max(left1, left2) + max(right1, right2)]
        
        steal, not_steal = dfs(root)
        return max(steal, not_steal)

LeetCode刷题笔记-动态规划-day3

文章目录

LeetCode刷题笔记-动态规划-day3

198. 打家劫舍

1.题目

原题链接:198. 打家劫舍

2.解题思路

我们可以用f[i]表示小偷走到第i个房间偷的总金额,可以分两种情况:

  1. 小偷不选第i个房间的现金,那么f[i]=f[i-1];
  2. 小偷选择偷第i个房间的现金,因为不能选择连续的房间,所以f[i]=f[i-2]+nums[i];

如图:

图转载:https://www.acwing.com/solution/content/19504/

3.代码

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

213. 打家劫舍 II

1.题目

原题链接:213. 打家劫舍 II

2.解题思路

从题意可知该题相比198. 打家劫舍只是多了一个条件:第一个房间和最后一个房间不能同时选,因此我们可以枚举这两种情况:

  1. 一定不选第一个,即求出[2,n]区间可以得到的最大价值
  2. 一定不选最后一个,即求出[1,n-1]区间可以得到的最大价值

最后取两种情况的最大值即可。

3.代码

class Solution 
public:
    int rob(vector<int>& nums) 
        int n=nums.size();
        vector<int> f(n+1);
        if(n<2) return nums[0];
        if(n==2) return max(nums[0],nums[1]);
        f[0]=nums[0],f[1]=max(nums[0],nums[1]);
        for(int i=2;i<n-1;i++)
            f[i]=max(f[i-1],f[i-2]+nums[i]);
        
        int t1=f[n-2];
        f.clear();
        f[0]=0,f[1]=nums[1];
        for(int i=2;i<n;i++)
            f[i]=max(f[i-1],f[i-2]+nums[i]);
        
        int t2=f[n-1];
        return max(t1,t2);
    
;

740. 删除并获得点数

1.题目

原题链接:740. 删除并获得点数

2.解题思路

仔细分析题目可知该题与198. 打家劫舍相似,不同的是每个元素可能有多个。

因为题目要求了如果选择num[i],就不能选择等于 nums[i] - 1nums[i] + 1 的元素,等价于198. 打家劫舍中我们不能选择连续的房间。

我们可以用map记录每个元素的个数,由题目给出的数字范围我们可以枚举1到10000的所有元素。用f[i]表示走到该点获取的最大点数,则:

f[i]=max(f[i-1],f[i-2]+i*cnt[i]);

3.代码

class Solution 
public:
    int deleteAndEarn(vector<int>& a) 
        map<int,int> cnt;
        for(auto x:a) cnt[x]++;
        vector<int> f(10001);
        f[1]=1*cnt[1];
        for(int i=2;i<10001;i++)
            f[i]=max(f[i-1],f[i-2]+i*cnt[i]);
        
        return f[10000];
    
;

以上是关于算法刷题打卡041 | 动态规划9-打家劫舍系列的主要内容,如果未能解决你的问题,请参考以下文章

算法刷题:LC初级算法动态规划类

算法刷题:LC初级算法动态规划类

LeetCode动态规划入门(专项打卡21天合集)

LeetCode动态规划入门(专项打卡21天合集)

LeetCode刷题笔记-动态规划-day3

LeetCode刷题笔记-动态规划-day3