Leetcode 打家劫舍系列

动态规划是算法中常常考察的内容. 开始学习这一块的内容. 跟着 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;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值