【动态规划】——打家劫舍问题

线性排列情况——打家劫舍I

198. 打家劫舍

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

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

示例 1:

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

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

解题思路

动态规划问题:状态选择

你面前房子的索引就是状态, 抢和不抢就是选择

在这里插入图片描述
在两个选择中, 每次都选更⼤的结果, 最后得到的就是最多能抢到的money:

// 主函数
public int rob(int[] nums) {
	return dp(nums, 0);
}

// 返回 nums[start..] 能抢到的最⼤值
private int dp(int[] nums, int start) {
	if (start >= nums.length) {
		return 0;
	} 
	int res = Math.max(
			// 不抢, 去下家
			dp(nums, start + 1),
			// 抢, 去下下家
			nums[start] + dp(nums, start + 2)
	);
	return res;
}


  • 采用备忘录进行优化,消除重复子问题
    在这里插入图片描述
  • 自顶向下
	/**
     * 自顶向下
     */

    private int[] memo;
    // 主函数
    public int rob_4(int[] nums) {
        // 初始化备忘录
        memo = new int[nums.length];
        Arrays.fill(memo, -1);
        // 强盗从第 0 间房⼦开始抢劫
        return dp(nums, 0);
    }
    // 返回 dp[start..] 能抢到的最⼤值
    private int dp(int[] nums, int start) {
        if (start >= nums.length) {
            return 0;
        } // 避免重复计算
        if (memo[start] != -1) return memo[start];
        int res = Math.max(dp(nums, start + 1),
                nums[start] + dp(nums, start + 2));
        // 记⼊备忘录
        memo[start] = res;
        return res;
    }
  • 自底向上

    /**
     * 自底向上
     */
    public int rob_5(int[] nums){
        int n = nums.length;
        //dp[i] = x 表示:
        //从第i间房子开始抢劫,最多能抢到的钱为x
        //base case:dp[n] = 0
        int[] dp = new int[n + 2];
        for (int i = n - 1;i >= 0;i--){
            dp[i] = Math.max(dp[i + 1],nums[i] + dp[i + 2]);
        }
        return dp[0];
    }
  • 发现状态转移只和 dp[i] 最近的两个状态有关
    /**
     * dp[i] 只和dp[i] 最近的两个状态dp[i + 1] 和dp[i + 2]有关,
     */
    public int rob(int[] nums){
        int n = nums.length;
        //记录dp[i + 1] 和dp[i+2]
        int dpi1 = 0;
        int dpi2 = 0;
        //记录dp[i]
        int dpi = 0;
        for(int i = n - 1;i >= 0;i--){
            dpi = Math.max(dpi1,nums[i] + dpi2);
            dpi2 = dpi1;
            dpi1 = dpi;
        }
        return dpi;
    }

环形排列情况——打家劫舍II

213. 打家劫舍 II

题目描述

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

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

示例 1:

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

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

输入:nums = [0]
输出:0

解题思路

这些房子不是一排, 而是围成了一个圈

三种情况:

  • 要么都不被抢;
  • 要么第一间房被抢最后一间不抢;
  • 要么最后一间房被抢,第一间不抢。

在这里插入图片描述
只要比较情况二和情况三就行了, 因为这两种情况对于房子的选择余地比情况一大呀, 房子里的钱数都是非负数, 所以选择余地大, 最优决策结果肯定不会小。

class Solution_213 {
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 1)
            return nums[0];
        return Math.max(robRange(nums,0,n - 2),robRange(nums,1,n-1));
    }

    private int robRange(int[] nums, int start, int end) {
        int n = nums.length;
        int dpi1 = 0;
        int dpi2 = 0;
        int dpi = 0;
        for (int i = end; i >= start;i--){
            dpi = Math.max(dpi1,nums[i] + dpi2);
            dpi2 = dpi1;
            dpi1 = dpi;
        }
        return dpi;
    }
}

树形排列情况——打家劫舍III

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.

解题思路

class Solution_337 {
    Map<TreeNode,Integer> memo = new HashMap<>();
    public int rob(TreeNode root) {
        if (root == null)
            return 0;
        //利用备忘录消除重叠子问题
        if (memo.containsKey(root)){
            return memo.get(root);
        }
        //抢,然后去下下家
        int do_it = root.val + (root.left == null ? 0 : rob(root.left.left) + rob(root.left.right)) +
                (root.right == null ? 0 : rob(root.right.left) + rob(root.right.right));
        //不抢
        int not_do = rob(root.left) + rob(root.right);

        int res = Math.max(do_it,not_do);
        memo.put(root, res);
        return res;
    }
}
  int rob(TreeNode root){
        int[] res = dp(root);
        return Math.max(res[0],res[1]);
    }

    /**
     * 返回一个大小为2的数组arr
     * arr[0] 表示不抢root的话,得到的最大钱数
     * arr[1] 表示强root的话,得到的最大钱数
     * @param root
     * @return
     */
    private int[] dp(TreeNode root) {
        if (root == null){
            return new int[]{0,0};
        }
        int[] left = dp(root.left);
        int[] right = dp(root.right);
        //抢,下家就不能抢了
        int rob = root.val +left[0] + right[0];
        //不抢,下家可抢可不抢,取决于收益大小
        int not_rob = Math.max(left[0],left[1] + Math.max(right[0],right[1]));
        return new int[]{not_rob,rob};
    }
posted @ 2021-03-30 21:14  your_棒棒糖  阅读(58)  评论(0编辑  收藏  举报