js + leetcode刷题:No.198、213、337打家劫舍三题一文

leetcode198与面试题 17.16. 按摩师一样,而打家劫舍系列共有三道题目,还有两个分别是213, 337

一、198

题目:

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

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

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

解法:

/**
 * @param {number[]} nums
 * @return {number}
 */
// Solution One -- 68ms 34.2mb
var rob0 = function(nums) {
    let len = nums.length
    let a = b = 0
    for(let i = 0; i < len; i++){
        let c = Math.max(a, b + nums[i])
        b = a
        a = c
    }
    return a
};

// Solution Two-- 68ms 34.2mb
// core:dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i])
var rob1 = function(nums) {
    let len = nums.length
    if(len === 0) return 0
    else if(len === 1) return nums[0]
    let arr = new Array(len+1).fill(0)
    arr[0] = nums[0]
    arr[1] = Math.max(nums[0], nums[1])
    for(let i = 2; i < len; i++){
        arr[i] = Math.max(arr[i-1], arr[i-2] + nums[i])
    }
    return arr[len - 1]
};

// Solution Three -- 68ms 33.5mb
var rob2 = function(nums) {
    let len = nums.length
    if(len === 0) return 0
    let dp0 = 0, dp1 = nums[0]
    for(let i = 1; i < len; i++){
        let tdp0 = Math.max(dp0, dp1),
            tdp1 = dp0 + nums[i]
        dp0 = tdp0
        dp1 = tdp1
    }
    return Math.max(dp0, dp1)
};

// one和three是一致的思路

二、213

题目:

  1. 打家劫舍 II
    你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
    给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

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

解法:

环形使得第一和最后一个不能在同一次动态规划中。相当于进行两次198的打家劫舍的计算,比较出最大的一个
// 68ms 34.8mb
/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    let len = nums.length
    if(len === 0) return 0
    else if(len === 1) return nums[0]
    return Math.max(getRob(nums, 0, len-1), getRob(nums, 1, len))
};

var getRob = function(nums, start, end){
    let dp0 = 0, dp1 = nums[start]
    for(let i = start+1; i < end; i++){
        let tdp0 = Math.max(dp0, dp1),
            tdp1 = dp0 + nums[i]
        dp0 = tdp0
        dp1 = tdp1
    }
    return Math.max(dp0, dp1)
}

三、337

题目:

  1. 打家劫舍 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.

解法:

// Solution One -- 隔层偷,比较这层与下层偷到的最大值
// 2612ms 37.3mb
var rob0 = function(root) {
    if(root === null) return 0
    let money = root.val
    if(root.left !== null){
        money = money + rob(root.left.left) + rob(root.left.right)
    }
    if(root.right !== null){
        money = money + rob(root.right.left) + rob(root.right.right)
    }
    return Math.max(money, rob(root.left) + rob(root.right))
};

// 以上思路,发现太慢,如果数量过大,可能就超时了。参考网友(leetcode账户名reals)思路得之,有以下优化
// Solution Two -- 存储好已计算过的根节点
// 84ms  38.2mb
var rob = function(root){
    let nodeMoneyMap = new Map()
    return robMoney1(root, nodeMoneyMap)
}
var robMoney1 = function(root, nodeMoneyMap){
    if(root === null) return 0
    if(nodeMoneyMap.has(root)) return nodeMoneyMap.get(root)
    let money = root.val
    if(root.left !== null){
        money = money + robMoney1(root.left.left, nodeMoneyMap) + robMoney1(root.left.right, nodeMoneyMap)
    }
    if(root.right !== null){
        money = money + robMoney1(root.right.left, nodeMoneyMap) + robMoney1(root.right.right, nodeMoneyMap)
    }
    let resMoney = Math.max(money, robMoney1(root.left, nodeMoneyMap) + robMoney1(root.right, nodeMoneyMap))
    nodeMoneyMap.set(root, resMoney)
    return resMoney
}

// Solution Three -- 根据网友(leetcode账户名reals),还有一种思路
/*
使用一个大小为2的数组来表示 int[] res = new int[2] 0代表不偷,1代表偷
任何一个节点能偷到的最大钱的状态可以定义为
    当前节点选择不偷: 当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
    当前节点选择偷: 当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
表示为公式如下
    root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) + Math.max(rob(root.right)[0], rob(root.right)[1])
    root[1] = rob(root.left)[0] + rob(root.right)[0] + root.val;

作者:reals
*/
// 84ms 38.9mb
var rob2 = function(root){
    let rootRes = robMoney2(root)
    return Math.max(rootRes[0], rootRes[1])
}
var robMoney2 = function(root){
    if(root === null) return [0, 0]
    let res = new Array(2).fill(0),
        left = robMoney2(root.left),
        right = robMoney2(root.right)
    
    res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1])
    res[1] = left[0] + right[0] + root.val
    return res
}
不断学习,不断前行

你可能感兴趣的:(leetcode_js刷题,leetcode,动态规划,打家劫舍,javascript)