经典动态规划:打家劫舍系列问题House Robber盗贼

参考:

leetcode 337. 打家劫舍 III java

经典动态规划:打家劫舍系列问题

【LeetCode】打家劫舍系列(I、II、III)

线性村庄

import java.util.Arrays;

/**
 * https://www.cnblogs.com/gzshan/p/11188104.html
 * https://www.jianshu.com/p/11684bd0115e
 * <p>
 * 你是一个专业的小偷,计划偷窃沿街的房屋。
 * 每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,
 * 如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
 * 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
 */
public class RobbersLineTest {

    public static void main(String[] args) {
        int[] nums = {6, 9, 2, 6, 9, 4};

        System.out.println(rob(nums));
        System.out.println(rob2(nums));
        System.out.println(rob3(nums));

        System.out.println(rob4(nums, 0));

        // 初始化备忘录,所有元素为默认值-1
        memo = new int[nums.length];
        Arrays.fill(memo, -1);
        System.out.println(rob5(nums, 0));

        System.out.println(rob6(nums));
    }

    /**
     * 用一个二维数组记录访问每一个房间时可以得到的钱数,其中,
     * money[i][0]表示不抢当前房间可以得到的钱数,
     * money[i][1]表示抢劫当前房间可以得到的钱数。
     */
    public static int rob(int[] nums) {
        if (nums.length == 0)
            return 0;
        int[][] money = new int[nums.length][2];
        money[0][0] = 0;//不抢
        money[0][1] = nums[0];//抢
        for (int i = 1; i < nums.length; i++) {
            //不抢当前房间,前一个可以抢也可以不抢,取最大值
            money[i][0] = Math.max(money[i - 1][0], money[i - 1][1]);
            money[i][1] = money[i - 1][0] + nums[i];
        }
        return Math.max(money[nums.length - 1][0], money[nums.length - 1][1]);
    }


    /**
     * 动态规划
     */
    public static int rob2(int[] nums) {
        if (nums.length == 0)
            return 0;
        if (nums.length == 1)
            return nums[0];
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        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];
    }

    /**
     * https://mp.weixin.qq.com/s/4sh8lwsc-C1XaLGvkWyEmw
     */
    public static int rob3(int[] nums) {
        int sumOdd = 0;
        int sumEven = 0;

        for (int i = 0; i < nums.length; i++) {
            if (i % 2 == 0) {
                sumEven += nums[i];
                sumEven = Math.max(sumOdd, sumEven);
            } else {
                sumOdd += nums[i];
                sumOdd = Math.max(sumOdd, sumEven);
            }
        }
        return Math.max(sumOdd, sumEven);
    }

    /**
     * 递归
     */
    private static int rob4(int[] nums, int start) {
        if (start >= nums.length) {
            return 0;
        }

        return Math.max(
                // 不抢,去下家
                rob4(nums, start + 1),
                // 抢,去下下家
                nums[start] + rob4(nums, start + 2)
        );
    }

    private static int[] memo;

    /**
     * 递归 + 备忘录
     * 递归存在重叠子问题,可以用备忘录进行优化
     * 返回 rob[start..] 能抢到的最大值
     */
    private static int rob5(int[] nums, int start) {
        if (start >= nums.length) {
            return 0;
        }
        // 避免重复计算
        if (memo[start] != -1) return memo[start];

        int res = Math.max(
                rob5(nums, start + 1),
                nums[start] + rob5(nums, start + 2)
        );
        // 记入备忘录
        memo[start] = res;
        return res;
    }

    static int rob6(int[] nums) {
        int n = nums.length;
        // 记录 dp[i+1] 和 dp[i+2]
        int dp_i_1 = 0, dp_i_2 = 0;
        // 记录 dp[i]
        int dp_i = 0;
        for (int i = n - 1; i >= 0; i--) {
            dp_i = Math.max(dp_i_1, nums[i] + dp_i_2);
            dp_i_2 = dp_i_1;
            dp_i_1 = dp_i;
        }
        return dp_i;
    }

}

环形村庄

/**
 * https://www.cnblogs.com/gzshan/p/11188104.html
 * https://www.jianshu.com/p/11684bd0115e
 * <p>
 * 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。
 * 这个地方所有的房屋都【围成一圈】,这意味着第一个房屋和最后一个房屋是紧挨着的。
 * 同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
 */
public class RobbersCircleTest {

    public static void main(String[] args) {
        int[] nums = {6, 9, 2, 6, 9, 4};

        System.out.println(rob(nums));
        System.out.println(rob2(nums));
    }

    public static int rob(int[] nums) {
        /**
         * 思路:由于首尾也属于相邻,因此需要分别判断,以第一家是否打劫分成两个问题
         * 第一家抢:最后一家一定不能抢,从第0个到len-2做动态规划
         * 第一家不抢:从1到len-1做动态规划
         * 然后比较找出最大值
         */
        if (nums == null || nums.length == 0)
            return 0;
        int len = nums.length;
        if (len == 1)
            return nums[0];
        int[] dp1 = new int[len];
        int[] dp2 = new int[len + 1];

        //第一家抢
        dp1[0] = 0;
        dp1[1] = nums[0];
        for (int i = 2; i < len; i++)
            dp1[i] = Math.max(dp1[i - 1], dp1[i - 2] + nums[i - 1]);

        //第一家不抢
        dp2[0] = 0;
        dp2[1] = 0;
        for (int i = 2; i <= len; i++)
            dp2[i] = Math.max(dp2[i - 1], dp2[i - 2] + nums[i - 1]);

        return Math.max(dp1[len - 1], dp2[len]);
    }

    public static int rob2(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)
        );
    }

    // 仅计算闭区间 [start,end] 的最优结果
    private static int robRange(int[] nums, int start, int end) {
        int dp_i_1 = 0;
        int dp_i_2 = 0;
        int dp_i = 0;
        for (int i = end; i >= start; i--) {
            dp_i = Math.max(dp_i_1, nums[i] + dp_i_2);
            dp_i_2 = dp_i_1;
            dp_i_1 = dp_i;
        }
        return dp_i;
    }

}

二叉树村庄

import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;

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

    public static void main(String[] args) {
        TreeNode root = initTreeNode();

        System.out.println(rob(root));
        System.out.println(rob2(root));
        System.out.println(rob3(root));
    }

    @NotNull
    private static TreeNode initTreeNode() {
        TreeNode root = new TreeNode(3);

        TreeNode treeNode10 = new TreeNode(4);
        TreeNode treeNode11 = new TreeNode(5);

        TreeNode treeNode20 = new TreeNode(1);
        TreeNode treeNode21 = new TreeNode(3);
        TreeNode treeNode22 = new TreeNode(1);

        root.setLeft(treeNode10);
        root.setRight(treeNode11);

        treeNode10.setLeft(treeNode20);
        treeNode10.setRight(treeNode21);

        treeNode11.setLeft(treeNode22);
        //        3
        //       / \
        //      4   5
        //     / \   \
        //    1   3   1
        return root;
    }


    //动态规划
    //思路:
    //定义一个数组res,长度为2,
    // res[0]表示不抢该节点可获得最大值,
    // res[1]表示抢劫该节点可获得最大值
    //方法dp(root)意为:在以r为根节点的树中,返回抢劫根节点与不抢劫根节点可获得的最大值
    public static int rob(TreeNode root) {
        int[] res = dp(root);
        return Math.max(res[0], res[1]);
    }

    /**
     * 返回一个大小为 2 的数组 arr
     * arr[0] 表示不抢 root 的话,得到的最大钱数
     * arr[1] 表示抢 root 的话,得到的最大钱数
     */
    static 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};
    }


    //递归思想(不要深入递归函数体,只需知道递归函数的功能,以及找到跳出递归的边界条件)
    //思路:
    //能盗取的最高金额为 抢劫该节点+抢劫该节点的左孩子的左右子树+抢劫该节点的右孩子的左右子树
    //与 抢劫该节点的左子树+抢劫该节点的右子树的和  的最大值
    //执行用时 1005ms  原因是出现了很多重复的计算,可使用动态规划解决
    public static int rob2(TreeNode root) {
        if (root == null) return 0;
        int val = 0;
        if (root.left != null) val += rob(root.left.left) + rob(root.left.right);
        if (root.right != null) val += rob(root.right.left) + rob(root.right.right);
        return Math.max(rob(root.left) + rob(root.right), val + root.val);

    }

    static Map<TreeNode, Integer> memo = new HashMap<>();

    public static int rob3(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;
    }


    public static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x) {
            val = x;
        }

        public int getVal() {
            return val;
        }

        public void setVal(int val) {
            this.val = val;
        }

        public TreeNode getLeft() {
            return left;
        }

        public void setLeft(TreeNode left) {
            this.left = left;
        }

        public TreeNode getRight() {
            return right;
        }

        public void setRight(TreeNode right) {
            this.right = right;
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章