LeetCode《初级算法》动态规划之打家劫舍 -- JavaScript

题目

题目链接:leetcode-cn.com/leetbook/re…

image.png


题解

动态规划的分析步骤

1、划分边界,确定子问题

  • 当只有一间房时,直接偷;
  • 当有两间房时,偷金额高的;
  • 当有三间房时,可以先偷第一间,因为第二间不能偷,就回到一间房的问题;或者直接偷第二间或者第三间(这就是两间房的问题),然后结束;
  • 当有四间房时,可以先偷第一间,然后回到有两间房的问题;或者看成有三间房的问题;

2、定义优化函数,列出递推方程

设 f(n) 为长度为 n 的 nums 的一夜能偷到的最高金额;

f ( n ) = { n u m s [ 0 ] , n =1 M a x ( n u m s [ 1 ] , n u m s [ 0 ] ) , n =2 M a x ( n u m s [ 2 ] + f ( 1 ) , f ( 2 ) , n =3 M a x ( n u m s [ 3 ] + f ( 2 ) , f ( 3 ) ) , n =4 M a x ( n u m s [ k 1 ] + f ( k 2 ) , f ( k 1 ) ) , n =k f(n)= \begin{cases} nums[0], & \text{$n$=1}\\ Max(nums[1],nums[0]), & \text{$n$=2}\\ Max(nums[2]+f(1),f(2), & \text{$n$=3}\\ Max(nums[3]+f(2),f(3)), & \text{$n$=4}\\ Max(nums[k-1]+f(k-2),f(k-1)), & \text{$n$=k}\\ \end{cases}

由递推方程可知,此问题满足优化原则,可以使用动态规划;


1、递归实现

按照递推方程,写出递归的算法;

但是其时间和空间复杂度太高;

空间耗费来说,因为递归需要递归栈,并且每次传入的子数组都是使用 Array.prototype.slice() 方法生成的新数组,所以空间复杂度较大;

时间耗费来说,每次计算子问题都会重复计算另一个子问题已经计算过的问题,所以时间复杂度较大;

在 leetcode 编辑器中,以下算法对于规模小的问题来说可以正确得到结果,对于规模稍大的问题来说会报错超出时间限制;


/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    let len = nums.length;
    if(len === 1) {
        return nums[0];
    }else if(len === 2) {
        return Math.max(nums[0],nums[1]);
    }

    return Math.max(nums[len-1]+rob(nums.slice(0,len-2)),rob(nums.slice(0,len-1)))
};

复制代码

2、迭代实现

因为递归方法需要重复计算子问题,所以时间复杂度太大,这里使用迭代的方法,并使用一个数组 resultArr 记录每一次的子问题的解,以便减少时间复杂度;


/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    let len = nums.length;
    if(len === 1) {
        return nums[0];
    }else if(len === 2) {
        return Math.max(nums[0],nums[1]);
    }

    let resultArr = [nums[0],Math.max(nums[0],nums[1])]; // 用来存放各子问题的结果数组,以便减少重复计算;

    for(let i = 2;i < len;i++) {
        resultArr[i] = Math.max(nums[i] + resultArr[i-2],resultArr[i-1]);

    }

    return resultArr[len-1];    
};

复制代码

观察上面代码的循环迭代部分,每次计算规模为 n 的问题只需要用到前面规模为 n-1 和 n-2 的问题;

因此并不需要用一个数组来记录子问题的解,只需要两个变量记录就可以了;

如下:

/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    let len = nums.length;
    if(len === 1) {
        return nums[0];
    }else if(len === 2) {
        return Math.max(nums[0],nums[1]);
    }

    let result1 = nums[0],
        result2 = Math.max(nums[0],nums[1]); // 使用两个变量记录规模为 n-1 和 n-2 的子问题的解;
    let temp = null;

    for(let i = 2;i < len;i++) {
        
        temp = Math.max(nums[i] + result1,result2);
        result1 = result2;
        result2 = temp;
    }

    return result2;  
};

复制代码


大家如果有更好的思路和解法,欢迎大家一起来讨论啊~


这是使用 JavaScript 对 LeetCode《初级算法》的每道题的总结和实现的其中一篇,汇总篇在这里:

juejin.cn/post/700669…

Supongo que te gusta

Origin juejin.im/post/7033943980421152804
Recomendado
Clasificación