Day42 | 动态规划 :选或不选 打家劫舍&&打家劫舍II

Day42 | 动态规划 :选或不选 打家劫舍&&打家劫舍II

动态规划应该如何学习?-CSDN博客

动态规划学习:

1.思考回溯法(深度优先遍历)怎么写

注意要画树形结构图

2.转成记忆化搜索

看哪些地方是重复计算的,怎么用记忆化搜索给顶替掉这些重复计算

3.把记忆化搜索翻译成动态规划

基本就是1:1转换

文章目录

198.打家劫舍

[198. 打家劫舍 - 力扣(LeetCode)](https://leetcode.cn/problems/house-robber/description/)(https://leetcode.cn/problems/integer-break/description/)

思路分析:

树形结构图

先明确一下dp数组/dfs函数的含义,dpi就是在前i个房子里面打家劫舍,能得到的最高金额(就是题目要求的)

我们从最后一个房子倒着往前分析子问题

对于一个房子i,我们只有两种方案,选或者不选

选了的话,那我i-1就不能选了

不选的话,那我前i个房子可以得到的最大金额数量就和前i-1个房子可以得到的最大金额数量相等,因为第i个房子没选

所以可以很轻易的得出

cpp 复制代码
dp[i]=max(dp[i-1],dp[i-2]+nums[i]);

即在可能的两种方案中挑选一个最大值

1.回溯 DFS

1.返回值和参数

i是房子编号

nums是房子数字

dfs返回值就是前i个房子里面打家劫舍可以得到的最大金额(和dp数组含义相同)

cpp 复制代码
int dfs(int i,vector<int>& nums)

2.终止条件

可以看到我们递归到0就是叶子结点了,下标0是第一个房子,所以小于0就代表没有房子可以选了,没有房子可以选也就没有金额,就返回0

cpp 复制代码
if(i<0)
	return 0;

3.本层逻辑

如上所说,返回两者的最大值给上层递归函数就好

cpp 复制代码
return max(dfs(i-1,nums),dfs(i-2,nums)+nums[i]);

完整代码:

当然,这是超时的

cpp 复制代码
class Solution {
public:
    int dfs(int i,vector<int>& nums)
    {
        if(i<0)
            return 0;
        return max(dfs(i-1,nums),dfs(i-2,nums)+nums[i]);
    }
    int rob(vector<int>& nums) {
        return dfs(nums.size()-1,nums);
    }
};

2.记忆化搜索

就是搞一个哈希表dp,全都初始化为-1,每次返回前给哈希表dp赋值,碰到不是-1的那就是算过的,那就直接返回计算过的结果,不需要再次递归了

cpp 复制代码
class Solution {
public:
    int dfs(int i,vector<int>& nums,vector<int>& dp)
    {
        if(i<0)
            return 0;
        if(dp[i]!=-1)
            return dp[i];
        return dp[i]=max(dfs(i-1,nums,dp),dfs(i-2,nums,dp)+nums[i]);
    }
    int rob(vector<int>& nums) {
        vector<int> dp(nums.size(),-1);
        return dfs(nums.size()-1,nums,dp);
    }
};

3.1:1翻译为动态规划

1.确定dp数组以及下标的含义

dp数组就是前i个房子里面可以取得的最大金额

下标就是房子编号

2.确定递推公式

忘记了原因的请看思路分析部分

cpp 复制代码
dp[i]=max(dp[i-1],dp[i-2]+nums[i]);

3.dp数组如何初始化

第一种写法,dp数组的下标从0开始,在i=0和i=1我们的递推公式中会有不合法的状态,所以i需要从2开始

那么dp0和dp1就需要初始化,那么初始化为多少呢?

如果可以根据dp的含义想通如何初始化是最好的,比如前1间房子最大值就是nums0,前两间房子最大值就是max(nums0,nums1)

如果想不通的话,回到回溯法把0和1带进去就完事得出结果就完事

cpp 复制代码
class Solution {
public:
    int dfs(int i,vector<int>& nums)
    {
        if(i<0)
            return 0;
        return max(dfs(i-1,nums),dfs(i-2,nums)+nums[i]);
    }
    int rob(vector<int>& nums) {
        return dfs(nums.size()-1,nums);
    }
};

会得到如下的初始化方案:

cpp 复制代码
		vector<int> dp(nums.size(),0);
        dp[0]=nums[0];
        if(nums.size()>1)
            dp[1]=max(nums[0],nums[1]);
        else
            return dp[0];

第二种写法

dp数组的下标全部都+2(注意nums数组的下标是没变的,此时dp数组的2对应的是nums的0)

这样就避免了下标是负数的情况,完美贴合了dfs回溯的方法

cpp 复制代码
		vector<int> dp(nums.size()+2,0);
        for(int i=0;i<nums.size();i++)
            dp[i+2]=max(dp[i+1],dp[i]+nums[i]);

4.确定遍历顺序

后续结果需要依赖前面的计算结果,故使用从前往后遍历

cpp 复制代码
for(int i=2;i<nums.size();i++)
	dp[i]=max(dp[i-1],dp[i-2]+nums[i]);

完整代码

cpp 复制代码
class Solution {
public:
    int rob(vector<int>& nums) {
        vector<int> dp(nums.size(),0);
        dp[0]=nums[0];
        if(nums.size()>1)
            dp[1]=max(nums[0],nums[1]);
        else
            return dp[0];
        for(int i=2;i<nums.size();i++)
            dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
        return dp[nums.size()-1];
    }
};
cpp 复制代码
class Solution {
public:
    int rob(vector<int>& nums) {
        vector<int> dp(nums.size()+2,0);
        for(int i=0;i<nums.size();i++)
            dp[i+2]=max(dp[i+1],dp[i]+nums[i]);
        return dp[nums.size()+1];
    }
};

213.打家劫舍II

[213. 打家劫舍 II - 力扣(LeetCode)](https://leetcode.cn/problems/house-robber-ii/description/)(https://leetcode.cn/problems/integer-break/description/)

分两种情况,考虑是否偷 第一个房子:

如果偷 nums0,那么 nums1 和 numsnums.size()−1 不能偷,问题变成从 nums2 到 numsnums.size()−2 的非环形版本,直接调打家劫舍的代码

如果不偷 nums0,那么问题变成从 nums1 到 numsnums.size()−1 的非环形版本,直接调打家劫舍的代码

当然,如果元素数量<=2的话我们的begin+2直接是不合法的,那就做一个特殊处理就好

cpp 复制代码
class Solution {
public:
    int rob1(vector<int> nums) {
        vector<int> dp(nums.size()+2,0);
        for(int i=0;i<nums.size();i++)
            dp[i+2]=max(dp[i+1],dp[i]+nums[i]);
        return dp[nums.size()+1];
    }
    int rob(vector<int>& nums) {
        if(nums.size()==1)
            return nums[0];
        if(nums.size()==2)
            return max(nums[0],nums[1]);
        return max(
            nums[0]+rob1(vector<int>(nums.begin()+2,nums.end()-1)),
            rob1(vector<int>(nums.begin()+1,nums.end())));
    }
};
相关推荐
江畔柳前堤3 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore4 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考6 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX6 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面7 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号7 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽7 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI8 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者9 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者9 小时前
【征程6】校准量化中HistogramObserver解析
算法