0
点赞
收藏
分享

微信扫一扫

剑指offer89:房屋偷盗

酷子腿长一米八 2022-02-11 阅读 34

题目:
一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响小偷偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组 nums ,请计算 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例一:
输入:nums = [1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例二:
输入:nums = [2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
分析:
由于这个问题并没有要求列举出小偷所有满足条件的偷盗方法,而只是求最多能偷取的财务数量,也就是求问题的最优解,因此这个问题适合运用动态规划。
应用动态规划解决问题的关键在于找出状态转移方程,假设街道上有n幢房屋(分别用0~n-1标号),用f(i)表示小偷从标号为0的房屋开始到标号为i的房屋为止最多能偷取到的财务最大值,f(n-1)的值就是小偷从n幢房屋中能偷取到的最多财物的数量,这就是问题的解。小偷在标号为i的房屋前有两个选择,一个选择是他进去偷东西。由于街道上有报警系统,因此他不能进入相邻的标号为i-1的房屋偷东西,之前他最多能偷取的财务的最大值是f(i-2),因此小偷如果进入标号为i的房屋盗窃,他最多能偷取的财务最大值是f(i-2)+nums[i],另一个选择是小偷不进入标号为i的房屋,那么他可以进入标号为i-1的房屋内偷东西,因此此时他最多能偷取的财务的数量为f(i-1).那么小偷在到达标号为i的房屋时他能偷取的财务的最大值就是两个选项的最大值,即 f(i)=max(f(i-2)+nums[i],f(i-1))。
上述状态转移方程有一个隐含条件,假设i大于或等于2.当i等于0时,f(0)是街道上只有标号为0的一幢房屋时小偷最多能偷的财务的数量,f(1)=max(nums[0],nums[1])。
为了避免不必要的重复计算,可以创建一个数组dp,它的第i个元素dp[i]用来保存f(i)的结果。如果f(i)之前已经计算出结果,那么只需从数组dp中读取dp[i]的值,不能再重复计算。
代码:

import javax.crypto.spec.PSource;
import java.util.Arrays;

public class Rob {
    public int rob(int[] nums) {
        if (nums.length == 0){
            return 0;
        }
        int[] dp = new int[nums.length];
        //填充数组,将数组中的值都设置为-1
        Arrays.fill(dp,-1);
        helper(nums,nums.length-1,dp);
        return dp[nums.length-1];
    }

    private void helper(int[] nums, int i, int[] dp) {
        if (i == 0){
            dp[i] = nums[0];
        }else if (i == 1){
            dp[i] = Math.max(nums[0],nums[1]);
        }else if (dp[i] < 0){
            helper(nums,i-2,dp);
            helper(nums,i-1,dp);
            dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
        }
    }
    //换一种思路:先求出f(0)和f(1)的值,然后用f(0),f(1)的值求出f(2),用f(1)和f(2)的值求出f(3),以此类推,直至求出f(n-1)。用for循环来完
    //成这个自下而上的思路。
    public int rob1(int[] nums){
        if (nums.length == 0){
            return 0;
        }
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        if (nums.length > 1){
            dp[1] = Math.max(nums[0],nums[1]);
        }
        for (int i = 2; i < nums.length; i++) {
            dp[i] = Math.max(dp[i-1],dp[i-2] + nums[i]);
        }
        return dp[nums.length - 1];
    }
    //空间复杂度为O(1)的迭代代码
    public int rob2(int[] nums){
        if (nums.length == 0){
            return 0;
        }
        //dp[i]只需缓存两个值就足够了,并不需要一个长度为n的数组,因此可以进一步优化代码的空间效率。
        int[] dp = new int[2];
        dp[0] = nums[0];
        if (nums.length > 1){
            dp[1] = Math.max(nums[0],nums[1]);
        }
        //可以根据f(i-1)和f(i-2)的结果计算出f(i),然后用f(i)的结果写入数组原来保存f(i-2)的结果的位置。接下来用f(i-1)和f(i)的结果计算f(i+1),f(i-2)的值虽然会被覆盖,但以后再也不会用到这个值,因此不会带来任何问题。
        for (int i = 2; i < nums.length; i++) {
            dp[i%2] = Math.max(dp[(i-1)%2],dp[(i-2)%2] + nums[i]);
        }
        return dp[(nums.length-1)%2];
    }
    
}

举报

相关推荐

0 条评论