【PHP解法==LeetCode(动态规划3)】打家劫舍I,II,III(198.213.337) && 309. 最佳买卖股票时机含冷冻期

目录

198.打家劫舍

213.打家劫舍II

337.打家劫舍III

309. 最佳买卖股票时机含冷冻期


198.打家劫舍

绘制如下递归树,如考虑偷取[0 ... n-1]范围的所有房子,可以考虑偷取0,1,到n-1的房子。
如偷取0,则下一步则考虑偷取[2...n-1]的所有房子,因为1与0相邻,所以不考虑偷取

此递归树中,可以明显的看出有重叠子问题

状态转移方程:f(x)表示偷取[x ... n-1]范围里的最多的房子

/********  记忆化搜索  ********/
class Solution {
    private $nums,$len;             //初始化数组和长度
    private $memo = [];             //初始化记忆数组
    /**
     * @param Integer[] $nums
     * @return Integer
     */
    function rob($nums) {
        $this->nums = $nums;        //赋值成员变量
        $this->len = count($nums);
        return $this->tryRob(0);    //开始递归
    }
    //考虑抢劫 nums[index...$len]这个范围内的所有房子
    private function tryRob($index){
        if($index >= $this->len) return 0;
        if(isset($this->memo[$index]))    //重叠子问题,如果已经找过了,直接返回即可
            return $this->memo[$index];   //memo[i]表示考虑抢劫nums[i...n]所能获得的最大收益
        $res = 0;
        for($i = $index;$i<$this->len;++$i){
            $res = max($res,$this->nums[$i] + $this->tryRob($i + 2));   //考虑当前所获的收益和盗取后面两个所获得的最大收益取较大值
        }
        $this->memo[$index] = $res;
        return $res;
    } 
}
/********  动态规划  ********/
class Solution {
    /**
     * @param Integer[] $nums
     * @return Integer
     */
    function rob($nums) {
        $len = count($nums);        
        if($len == 0) return 0;          //初始化判断,当数组为空时,无法抢劫
        $dp = [];                        //dp[i]表示考虑抢劫nums[i...n]所能获得的最大收益
        $dp[$len-1] = $nums[$len-1];     //初始化盗取最后一个节点的值
        for($i = $len - 2;$i >= 0;--$i){ //从后往前,dp记录从每一个房间开始抢劫的最大金额数
            $dp[$i] = 0;                 //初始化新的盗取节点
            for($j = $i;$j<$len;++$j){   //考虑当前所获的收益和盗取后面两个所获得的最大收益取较大值
                $dp[$i] = max($dp[$i], $nums[$j] + $dp[$j+2]);
            }
        }
        return $dp[0];    //开始递归
    }
}

213.打家劫舍II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

与198区别,这是一个环形的小区,说明第一个房屋和最后一个房屋不能同时盗取,因此需要考虑两种情况

(1)考虑偷取 [ 0 , n - 2 ] 的房屋     (2)考虑偷取 [ 1, n - 1 ] 的房屋

取大者即为答案

class Solution {
    /**
     * @param Integer[] $nums
     * @return Integer
     */
    function rob($nums) {
        $len = count($nums);
        if($len == 0) return 0;            //初始化判断,当数组为空时,无法抢劫
        if($len == 1) return $nums[0];     //当数组长度为 1时,只有一种盗取可能
        $dp = [];                          //dp[i]表示考虑抢劫nums[i...n]所能获得的最大收益
        //先考虑偷取[0...n-2]的房子
        $dp[$len - 2] = $nums[$len - 2];   //初始化盗取 n-2 节点的值
        for($i = $len - 3;$i >= 0;--$i){   //从后往前,dp记录从每一个房间开始抢劫的最大金额数
            $dp[$i] = 0;                   //初始化新的盗取节点
            for($j = $i;$j<=$len - 2;++$j){//考虑当前所获的收益和盗取后面两个所获得的最大收益取较大值
                $dp[$i] = max($dp[$i], $nums[$j] + $dp[$j+2]);
            }
        }
        $max = $dp[0];                     //保存偷取[0...n-2]的房子的最大收益
        $dp = [];
        //考虑偷取[1...n-1]的房子
        $dp[$len - 1] = $nums[$len - 1];   //初始化盗取 n-1 节点的值
        for($i = $len - 2;$i >= 1;--$i){
            $dp[$i] = 0;                   //初始化新的盗取节点
            for($j = $i;$j<=$len - 1;++$j){
                $dp[$i] = max($dp[$i], $nums[$j] + $dp[$j+2]);
            }
        }
        return max($max, $dp[1]);           //两者取大值即可
    }
}

337.打家劫舍III

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:

输入: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \ 
     3   1

输出: 7 
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:

输入: [3,4,5,1,3,null,1]

     3
    / \
   4   5
  / \   \ 
 1   3   1

输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

递归分析两种情况:

(1)盗取当前节点,则不盗取当前节点的左右节点

(2)不盗取当前节点,则从盗取当前节点的左右节点中选择较大值

/****************  递归:超出输出限制  ****************/
class Solution {
    /**
     * @param TreeNode $root
     * @return Integer
     */
    function rob($root) {
        if($root == null) return 0;
        //计算盗取左孩子的左右孩子的最大值
        if($root->left) $left = $this->rob($root->left->left) + $this->rob($root->left->right);  
        //计算盗取右孩子的左右孩子的最大值   
        if($root->right) $right = $this->rob($root->right->left) + $this->rob($root->right->right);
        //计算盗取 包含该节点 和 不盗取该节点 的两种情况的最大值
        return max($root->val+$left+$right,$this->rob($root->left)+$this->rob($root->right));
    }
}
/****************  记忆化搜索  ****************/
class Solution {
    /**
     * @param TreeNode $root
     * @return Integer
     */
    function rob($root) {
        $res = $this->findMax($root);
        return max($res);
    }
    private function findMax($root){
        $res = [0,0];                               //用$res 来标记抢和不抢root的所获得的最大值
        if($root == null) return $res;              //递归结束标志
        $left = $this->findMax($root->left);        //盗取左孩子
        $right = $this->findMax($root->right);      //盗取右孩子
        $res[0] = $root->val + $left[1] + $right[1];//盗取该节点时,则不盗取该盗取该节点的左右孩子
        $res[1] = max($left) + max($right);         //不盗取该节点时,则无所谓盗不盗取该节点的左右孩子,大就行
        return $res;
    }
}

309. 最佳买卖股票时机含冷冻期

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:

输入: [1,2,3,0,2]
输出: 3 
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

分析:参考【LeetCode】309. Best Time to Buy and Sell Stock with Cooldown解题思路

状态转移,首先要明白只有三种状态,买入buy,卖出sell,冷冻期rest

我们这里把状态分成了三个,根据每个状态的指向

每个状态的表示如下

S0:sell后的rest 、 rest后的rest

S1:rest后的buy 、 buy后的rest

S2:rest后的sell 、 buy后的sell

可以得出下面的状态转移方程 : 分别代表三种状态下的最大利润值

S0[i] = max(S0[i-1], S2[i-1])                :取S0(继续休息)和S2(卖出后的休息)的最大利润值值

S1[i] = max(S1[i-1], S0[i-1] - price[i]) :buy的最大利润值为max(rest期间价格不变,buy后的rest则需要付费该股票)

S2[i] = S1[i-1] + price[i]                       :  S1 状态的最大利润加上卖出的金额

class Solution {
    /**
     * @param Integer[] $prices
     * @return Integer
     */
    function maxProfit($prices) {
        $len = count($prices);  
        if($len <= 1) return 0;                  //初始化判断,长度为<=1,无法进行卖出,则无收益
        $s0 = 0;                                 //rest 冷冻期状态:初始化利润为0
        $s1 = -$prices[0];                       //buy  买入状态  :初始化为第一个股票价格的负值
        $s2 = 0;                                 //sell 卖出状态  :初始化为0,因为还没有买卖
        for($i = 1;$i<$len;++$i){
            $pre0 = $s0;                         //用pre代表[i-1]的状态
            $pre1 = $s1;    
            $pre2 = $s2;
            $s0 = max($pre0,$pre2);              //取S0(继续休息)和S2(卖出后的休息)的最大利润值值
            $s1 = max($pre0 - $prices[$i],$pre1);//buy的最大利润值为max(rest期间价格不变,buy后的rest则需要付费该股票)
            $s2 = $pre1 + $prices[$i];           //S1 状态的最大利润加上卖出的金额
        }
        return max($s0,$s2);                     //最大的利润最后出现在sell和rest之间,不可能出现在buy,即s1不考虑
    }
}

动态规划的难点在于要清楚有哪些状态,已经每个状态是如何转移的,把大问题化为一个又一个的小问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值