打家劫舍Ⅰ、Ⅱ、Ⅲ

动态规划的关键是:找出子问题,子问题可以通过子问题求解出来

动态规划的的四个解题步骤是:

1)定义子问题

2)写出子问题的递推关系

3)找出边界关系

4)空间优化(可选)

1 打家劫舍

  • 题目
    你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
  • 思路
    1)子问题:原来的问题是“从全部房子中能偷到的最大金额”,将问题的规模缩小,子问题就是“从 k个房子中能偷到的最大金额”,用 f(k)表示。
    2)子问题的递推关系:
    有n个房子,每个房子的金额分别是nums[0]、nums[1]、…、nums[n-1],
    子问题就是“从 k个房子中能偷到的最大金额”f(k)。
    有两种偷法:一是偷第k个房子nums[k-1],那么问题就变成在前 k-2个房子中偷到的最大的金额。二是不偷第k个房子,问题就变成在前 k-1个房子中偷到最大的金额,也就是子问题f(k−1)。两种方法选择金额较大的一种结果。

    递推关系是:f(k)=max(f(k-2)+nums[k-1],f(k-1))//数组下标从0开始
    找出边界:第0个房子时,f(0)=0;第一个房子时f(1)=nums[0]
    3)找出计算顺序:(代码的实现)只需要按 k 从小到大依次计算子问题即可。
    4)空间优化

    修改:
let preTwo=nums[0]
let preOne=Math.max(nums[1],nums[0])
  • 代码
//动态规划
function rob_2(nums) {
    if(nums.length <= 0){
        return false;
    }
    let result; //保存结果
    let preTwo = 0;  //用来表示当前房间往前第二个房间时的最优解,初始化为第0间金额为0
    let preOne = nums[0];  //用来表示当前房间往前第二个房间时的最优解,初始化为第0间金额为nums[0]
    if(nums.length === 1){
        result = nums[0];
    }
    //自di向上循环数组
    for(let i= 1; i < nums.length; i++){
        result = Math.max(preTwo + nums[i], preOne);
        preTwo = preOne;
        preOne = result;
    }
    return result;
}
//奇偶数的动态规划
let rob = function(nums) {
    if(nums.length <= 0){
        return false;
    }
    let result; //保存将要返回的结果
    let sum = [0, 0];//数组的第一个数保存奇数的和,第二保存偶数的和
    if(nums.length === 1){
        result = nums[0];
    }
    for(let i = 0; i < nums.length; i++){
        if(i % 2 === 0){
            //奇数时求和
            sum[0] +=nums[i];
            sum[0] = Math.max(sum[0], sum[1]);
        }
        if(i % 2 !== 0){
            //偶数求和
            sum[1] +=nums[i];
            sum[1] = Math.max(sum[0], sum[1]);
        }
    }
    result = Math.max(sum[0], sum[1]);
    return result;
};

2 打家劫舍Ⅱ

  • 房屋围成一圈
  • 代码
 //需要考虑两种情况,有头无尾或者有尾无头,取两者中的较大值
var rob = function(nums) {
    if(nums.length==1) return nums[0]
    let a=nums.slice(0,nums.length-1)
    let b =nums.slice(1)
    let res1=find(a),res2=find(b)
    return Math.max(res1,res2)
};
function find(nums){
    let result
    let preone=nums[0]
    let pretwo=0
    if(nums.length==1) result=nums[0]
    for(let i=1;i<nums.length;i++){
        result=Math.max(nums[i]+pretwo,preone);
        pretwo=preone
        preone=result
        
    }
    return result
}

打家劫舍3

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

  • 思路
    本题可分为两种情况,当前节点的值是偷还是不偷,所以我们用一个数组来表示,数组的第一位是偷,数组的第二位是不偷。并且本题我们采用从下向上进行访问。当前节点不存在时,则返回[0, 0],当存在时,则进行左节点和右节点的递归。
    1)房屋是树形结构,采用何种方式遍历遍历呢?由于要知道当前节点的最大金额,我们需要知道其左右子节点的最大金额,因此左右子节点要先遍历,中间节点后遍历,故采用后序遍历
    2)后序遍历使用递归

    • 递归的返回值?返回该节点的偷与不偷两种金额结果,用长度为2的数组保存,[偷的金额,不偷的金额]
    • 递归结束条件?当遍历到空节点,返回[0,0]
    • 单层递归的逻辑?分两种情况
      ** 偷当前节点获得的最大金额 = 左节点两个孩子节点的最大金额 + 右节点两个孩子节点的最大金额
      ** 不偷当前节点的最大金额 = 左节点的最大金额(偷与不偷取最大的) + 右节点的最大金额(偷与不偷取最大的)
  • 代码

var rob = function(root) {
    function robTree(node){
        // 节点为空,偷与不偷都为0
        if(!node) return [0,0]
        let left = robTree(node.left)
        let right = robTree(node.right)
        // 偷当前节点 左节点不偷、右节点不偷
        let val1 = node.val + left[1] + right[1]
        // 不偷当前节点,左右节点可偷可不偷,结果最大就好
        let val2 = Math.max(left[0] , left[1]) + Math.max(right[0] , right[1])
        return [val1 , val2]
    }
    let res = robTree(root)
    return Math.max(res[0] , res[1])
};
posted @ 2022-09-22 21:54  衣囧~  阅读(65)  评论(0)    收藏  举报