算法第34天|动态规划:打家劫舍Ⅰ、打家劫舍Ⅱ、打家劫舍Ⅲ
今日总结:
打家劫舍
1、如果遇到了对dp数组的前几个值定义,在进行遍历时,要思考前几个值还需不需要遍历
2、这道题其实没有出现,前边的题型中出现过
dp递推公式中出现min:在初始化的时候一定要注意普通位置要初始化为INT_MAX而不能是0.
打家劫舍Ⅱ
1、增加了额外条件圆形,所以分情况讨论:放首不放尾,放尾不放首,首尾都不放2、注意:dp[start] = max(nums[start],nums[start+1]);
打家劫舍Ⅲ(重点复习)
打家劫舍Ⅰ
题目链接:198. 打家劫舍 - 力扣(LeetCode)
代码随想录
整体思路:
通过题目可知:
1、影响偷窃的唯一因素就是不能偷相邻的房屋。
2、需要在不偷窃相邻房屋的情况下偷窃最高的金额
所以,可以将题目抽象:
1、我偷房间i,房间i-1一定是不能被偷的,所以是房间i-2偷的最多的钱+房间i的钱,所以这道题是当前状态与之前状态有关-->动态规划问题
动态规划五部曲确定这道题
1、确定dp数组及下标
dp[i]:表示在[0,i]的房间中偷窃在不触发警报的条件下能偷窃的最大金额
2、确定dp数组的递推公式(状态转移方程)
dp[i]与前边两个房间有关
如果要偷房间i的钱,就不能偷房间i-1,所以要计算房间i-2的最大金额
dp[i] = dp[i-2]+nums[i]
如果不偷房间i,就需要统计房间i-1的最大金额
dp[i] = dp[i-1]
所以dp递推公式:
dp[i] = max(dp[i-1],dp[i-2]+nums[i])
3、确定dp数组的初始化
通过递推公式可知dp[i]的获取最终归结到dp[0]和dp[1]
dp[0]表示在房间0之前的房间中不触发警报能偷窃的最大金额
dp[0] = nums[0]
dp[1]表示在[0,1]的房间中,不触发警报(不连续偷房间)的最大金额
dp[1] = max(dp[0],dp[1])
4、确定dp数组的遍历条件
房间从0-nums.size()
整体代码:
class Solution { public:int rob(vector<int>& nums) {//定义dp数组vector<int>dp(nums.size(),0);//初始化if(nums.size()==0)return 0;else if(nums.size()==1) return nums[0];dp[0] = nums[0];dp[1] = max(nums[0],nums[1]);//遍历for(int i=2;i<nums.size();i++){//递推公式计算dp[i] = max(dp[i-1],dp[i-2]+nums[i]);}return dp[nums.size()-1];} };
打家劫舍Ⅱ
题目链接:213. 打家劫舍 II - 力扣(LeetCode)
代码随想录
整体思路:
相较于打家劫舍Ⅰ相比,只是多了一个条件:
现在的房屋是一个圆环形,也就是说首尾两间房不能同时都选择,最多选择一间或者都不选
1、选择首房,一定不能选择尾房,所以钱最多从dp[nums.size()-1]中获得
2、选择尾房,一定不能选择首房,也就是说dp数组是从dp[1]开始计算的
3、首尾都不选择:其实已经被1、2包括了,因为1中选择首房中也不一定选择首房,需要比较怎样获得的钱最多,2中同理
所以需要将打家劫舍Ⅰ中的内容写成函数,比较max(fun(1),fun(2))
整体代码:
class Solution { public://定义打家劫舍函数//返回值:最大的金额int//输入值:数组、数组的起始、终止int dajiajieshe(vector<int>&nums ,int start,int end){//首先判断传入的数据是不是0或者1if(end-start==0)//说明只有一个数return nums[start];else if(end-start==1)//说明只有两个数return max(nums[start],nums[end]);//定义dp数组,大小仍旧是nums.size(),不要因为首尾不同而发生变化,将对应值填入对应的dp位置才在最后能比较vector<int>dp(nums.size(),0);//初始化dp[start] = nums[start];dp[start+1] = max(dp[start],nums[start+1]);//遍历for(int i=start+2;i<=end;i++){dp[i] = max(dp[i-1],dp[i-2]+nums[i]);}return dp[end];}int rob(vector<int>& nums) {if(nums.size()==0)return 0;else if(nums.size()==1)return nums[0];int res1 =dajiajieshe(nums,0,nums.size()-2);int res2 =dajiajieshe(nums,1,nums.size()-1);return max(res1,res2);} };
打家劫舍Ⅲ
题目链接:337. 打家劫舍 III - 力扣(LeetCode)
代码随想录
整体思路:
//整体来说:
1、如果我偷了一个节点,那么我就不能偷这个节点的子节点
2、如果我不偷这个节点,那么我可以偷这个节点的子节点,也可以不偷这个节点的子节点(取最大值)
所以当前节点的状态与下边节点的值有关-->dp;同时因为是树形结构,且需要从上到下-->后序遍历
dp数组就是max(偷,不偷)
当前节点的值:偷当前节点-->取下下个子节点的最大+当前节点的值
不偷当前节点-->取下个子节点的最大
下下个子节点其实也存在偷与不偷的关系,所以可以对每个节点记录偷当前节点与不偷当前节点所达到的最大的值-->2位数组记录偷与不偷的值,然后后序遍历-->可以得到根节点的最大钱数max(偷,不偷)
整体代码:
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/ class Solution { public://整体来说://1、如果我偷了一个节点,那么我就不能偷这个节点的子节点//2、如果我不偷这个节点,那么我可以偷这个节点的子节点,也可以不偷这个节点的子节点(取最大值)//所以当前节点的状态与下边节点的值有关-->dp;同时因为是树形结构,且需要从上到下-->后序遍历//后序遍历,需要知道一个节点的偷的值,不偷的值://1、确定后序遍历的返回值和参数//返回值是2位数组:存储偷的钱,不偷的钱,当前的节点可以根据之前的返回值,计算对应的偷当前节点、不偷当前节点的钱,便于上边的节点计算//参数:当前节点vector<int> digui(TreeNode* root){//2、确定返回值:后序遍历需要遍历到叶子节点下的空节点返回0if(root==nullptr)return vector<int>{0,0};//3、单层递归逻辑//后序遍历,先遍历前、后vector<int> left = digui(root->left);vector<int> right = digui(root->right);//开始判断当前层偷与不偷的钱 不偷0 ,偷1//不偷当前层,可以偷下一层,也可以不偷下一层,看谁的钱多int no_cur = max(left[1],left[0])+max(right[1],right[0]);//偷当前层,就不能偷下一层,可以偷下下层int yes_cur = root->val+left[0]+right[0];//返回当前层return {no_cur,yes_cur};}int rob(TreeNode* root) {vector<int>res = digui(root);//偷不偷的钱都在res,判断偷root的多,还是不偷root的多return max(res[0],res[1]);} };