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

Leetcode 打家劫舍系列

程序员文章站 2022-03-05 16:01:30
...

动态规划是算法中常常考察的内容. 开始学习这一块的内容. 跟着 Leetcode 上的题目来学习. 

本文参考了 大佬的文章, 上面有详细完整的解释, 可以去看看. 

 

198. 打家劫舍

本题的思路较为简单, 用 dp[i] 表示子问题, 就是偷到第 i 家时, 小偷能偷到的最多的钱. 因为不能同时偷两间相同的房屋. 所以 dp[i] 的值有两个可能, 选择偷第 i 家的话, 那么一定不能偷 i - 1 家, 如果不偷第 i 家的话, 那么就可以偷第 i - 1 家.

dp[i] = Math.max(dp[i-1], dp[i - 2] + num[i])

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        // 因为会取到 dp[nums.length]
        int dp[] = new int[nums.length + 1];
        dp[0] = 0;
        dp[1] = nums[0];
        for(int i = 2; i <= nums.length; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i-1]);
        }
        return dp[nums.length];
    }
}

从递归关系式里可以看到, 当前 dp [i] 只与 dp[i -1] 和 dp[i- 2] 相关, 所以这里可以使用三个整数来替代数组, 减小空间复杂度

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        int prev = 0;
        int curr = 0;
        for(int x : nums) {
            int temp = curr;
            // 给 curr 赋的新值表示 dp[i]
            // curr 的旧值表示 dp[i - 1]
            // prev 表示 dp[i - 2]
            curr = Math.max(curr, prev + x);
            prev = temp;
        }
        return curr;
    }
}

213. 打家劫舍 ||

这里其实就是上面一题的进一步的情况, 因为第一个房间和最后一个房间是紧挨着的, 所以如果分为两种情况 :

1. 选择的范围包括第一间房子, 但是不包括最后一间房子;

2. 选择的范围包括最后一间房子, 但是不包括第一间房子;

所以可以将这两种情况分别计算一遍, 然后取两者的较大值即可.

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 0)   return 0;
        if (nums.length == 1)   return nums[0];
        int max1 = robhelper(Arrays.copyOfRange(nums, 0, nums.length - 1));
        int max2 = robhelper(Arrays.copyOfRange(nums, 1, nums.length));
        return Math.max(max1, max2);
    }
    public int robhelper(int[] arr) {
        int prev = 0;
        int curr = 0;
        for(int x : arr) {
            int temp = curr;
            curr = Math.max(curr, prev + x);
            prev = temp;
        }
        return curr;
    }
}

337.打家劫舍

本题就直接将树的情景放进来了. 相邻的节点的是不能同时打劫的. 看了高票的题解, 来说一下这题的思路, 用 F (

首先考虑通用的情况 : 三层的二叉树, 一个爷爷节点, 两个父节点, 四个孩子节点. 

如果访问了爷爷节点 : 那么我们只能访问四个孩子节点. 

或者我们访问这两个父节点.

那么对应的表达式就是 dp[root] = dp[root.left.left] + dp[root.left.right] + dp[root.right.left] + dp[root.right.right] + root.val;

dp[root] = dp[root.left] + dp[root.right] ; 比较这两个值的大小, 就是 dp[root].

那么按这个思路下来的直白版本 :

class Solution {
    public int rob(TreeNode root) {
        return robHelper(root);
    }
    public int robHelper(TreeNode root) {
        if (root == null) return 0;
        int money = root.val;
        if (root.left != null) {
            money += (robHelper(root.left.left) + robHelper(root.left.right));
        }
        if (root.right != null) {
            money += (robHelper(root.right.left) + robHelper(root.right.right));
        }
        return Math.max(money, (robHelper(root.left) + robHelper(root.right)));
    }
}

但是上面的思路的时间复杂度过高. 主要在于重复的计算, 在计算 root 的时候, 同时也计算了 root.left 和 root.right, 以及其子结点, 所以可以使用一个 hashMap 来存储求过的值. 减少时间复杂度. 

class Solution {
    public int rob(TreeNode root) {
        HashMap<TreeNode, Integer> map = new HashMap<>();
        return robHelper(root, map);
    }
    public int robHelper(TreeNode root, HashMap<TreeNode, Integer> map) {
        if (root == null) return 0;
        if (map.containsKey(root)) {
            return map.get(root);
        }
        int money = root.val;
        if (root.left != null) {
            money += (robHelper(root.left.left, map) + robHelper(root.left.right, map));
        }
        if (root.right != null) {
            money += (robHelper(root.right.left, map) + robHelper(root.right.right, map));
        }
        int result = Math.max(money, (robHelper(root.left, map) + robHelper(root.right, map)));
        map.put(root, result);
        return result;
    }
}

还有一种思路, 就是每个节点可以选择取或者不取, 用一个长度为 2 的数组 result 来表示取或者不取的结果, result[0] 表示不取, result[1] 表示取. 

root 节点的 result[0]  = root.left取或者不取的最大值, 加上 root.left 取或者不取的最大值. (因为这里 左右子结点可以选或者不选)

root 节点的 result[1] = root.val + root.left 不选的值 + root.right 不选的值, (因为选了 root , 所以左右子结点都不能选)

class Solution {
    public int rob(TreeNode root) {
        int[] res = robHelper(root);
        return Math.max(res[0], res[1]);
    }
    public int[] robHelper(TreeNode root) {
        if (root == null)   return new int[2];
        int[] result = new int[2];
        // 来取左右子结点的结果
        int[] left = robHelper(root.left);
        int[] right = robHelper(root.right);
        result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        // 因为left 和 right 子结点都不能选
        result[1] = left[0] + right[0] + root.val;
        return result;
    }
}