欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【LeetCode】每日一题(一)打家劫舍系列 动态规划

程序员文章站 2022-07-12 09:09:21
...

198. 打家劫舍

20200529

难度:简单

题目描述

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

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

示例:

示例 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 。

Solution

动态规划

dp[i] 表示前 i间房屋能偷窃到的最高总金额

  1. 偷窃第 k间房屋,那么就不能偷窃第 k-1间房屋,偷窃总金额为前 k-2间房屋的最高总金额与第 k 间房屋的金额之和。
  2. 不偷窃第 k间房屋,偷窃总金额为前 k-1间房屋的最高总金额。
class Solution {
    public int rob(int[] nums) { 
        if(nums == null || nums.length == 0){
            return 0;
        }else if(nums.length == 1){
            return nums[0];
        }
        
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        dp[1] = Math.max(dp[0], nums[1]);
        for(int i = 2; i < nums.length; i++){
            dp[i] = Math.max(nums[i] + dp[i-2], dp[i-1]);
        }
        return dp[nums.length-1];
    }
}

考虑到每间房屋的最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组,在每个时刻只需要存储前两间房屋的最高总金额

class Solution {
    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int length = nums.length;
        if (length == 1) {
            return nums[0];
        }
        int first = nums[0], second = Math.max(nums[0], nums[1]);
        for (int i = 2; i < length; i++) {
            int temp = second;
            second = Math.max(first + nums[i], second);
            first = temp;
        }
        return second;
    }
}

213. 打家劫舍 II

难度:中等

题目描述

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

示例:

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

Solution

动态规划。和198题的区别在于收尾相连。则转化为两个单排列问题:

  1. 不选第一个房子,即 nums[1:]
  2. 不选最后一个房子,即nums[0:n-1]

两者取最大值

class Solution {
    public int rob(int[] nums) {
        if(nums == null) return 0;
        int n = nums.length;
        if(n == 0){
            return 0;
        }else if(n == 1){
            return nums[0];
        }
        int max1 = robNormal(Arrays.copyOfRange(nums, 1, n));
        int max2 = robNormal(Arrays.copyOfRange(nums, 0, n-1));
        return Math.max(max1, max2);
    }
    public int robNormal(int[] nums){
        if(nums == null || nums.length == 0){
            return 0;
        }
        int length = nums.length;
        if(length == 1){
            return nums[0];
        }
        int first = nums[0];
        int second = Math.max(nums[0], nums[1]);
        for(int i = 2; i < length; i++){
            int tmp = second;
            second = Math.max(first + nums[i], second);
            first = tmp;
        }
        return second;
    }
}

337. 打家劫舍 III

难度:中等

题目描述

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

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

示例:

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

     3
    / \
   2   3
    \   \ 
     3   1

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

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

     3
    / \
   4   5
  / \   \ 
 1   3   1

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

Solution

树形结构的动态规划。

对每个节点来说,有两种状态,偷和不偷,两者取max。

int[] result = new int[2]来存两种状态下的收益。

  1. 偷当前节点:最大收益 = 当前节点的钱 + 不偷左儿子的钱 + 不偷右儿子的钱

  2. 不偷当前节点:最大收益 = 左儿子所能获得的最大收益 + 右儿子所能获得的最大收益

    其中,儿子所能获得的最大收益 = max(偷当前儿子所能获得的最大收益, 不偷当前儿子所能获得的最大收益)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
        int[] result = countSum(root);
        return Math.max(result[0], result[1]);
    }
    public int[] countSum(TreeNode root){
        int[] result = new int[2];
        if(root == null){
            return result;
        }
        // 当前节点左儿子偷与不偷所能获得的收益
        int[] left = countSum(root.left);
        // 当前节点右儿子偷与不偷所能获得的收益
        int[] right = countSum(root.right);
        // 不偷当前节点的收益 = 左儿子所能获得的最大收益 + 右儿子所能获得的最大收益
        result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        // 偷当前节点的收益 = 当前节点的钱 + 不偷左儿子的钱 + 不偷右儿子的钱
        result[1] = root.val + left[0] + right[0];
        return result;
    }
}