壹 ❀ 引

本题来自LeetCode198. 打家劫舍,难度中等,也很有意思,是一道教小偷如何偷窃最大金额的题,题目描述如下:

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

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

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

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 400

让我们简单分析题意,然后想办法实现它。

贰 ❀ 动态规划

本题其实是一道标准的动态规划题目,以局部最优解来求出全局最优解。假设给你一个非有序的数组,让你求出数组中的最小值,不允许排序,不允许使用Math.min你还能怎么做呢?这里就可以借用动态规划。

假设有个数组[3,0,2,4,1],我们可以假设数组第0位就是最小值(min),然后开始从第一位开始遍历(i=1),比较nums[i]min,如果nums[i]更小,那我们就更新min,反之不用更新,i自增。

因为min的存在,我们要知道到第i位的最小值,其实只需要比较num[i]min谁更小即可,因为min已经包含了i-1之前所有位数的最小值,这大概就是一个动态规划最基本的例子。

let findMin = function (nums) {
let min = nums[0];
for (let i = 1; i < nums.length; i++) {
if (nums[i] < min) {
min = nums[i];
}
};
return min;
}

让我们回到问题本身,提取下题目信息,小偷不能连着偷两家,所以从偷第一家的时候,就存在两种情况。我们把这个问题先简单化,比如[1,4,2]

第一种情况,小偷从第一家开始偷,然后偷第三家。

第二种情况,小偷从第二家开始偷。

我们假设到dp[i]是能偷到的最大收益,偷到第三家的时候,能偷到的最大收益是以上两种偷法之间的最大值,也就是:

// i=2时
// dp[i-1]是直接偷第二家的收益
// dp[i-2]是直接偷第一家的收益
dp[i] = Math.max(dp[i-1], dp[i - 2] + nums[i]);

翻译过来就是,偷到第三家时,是第一家的收益加上第三家自己(nums[2])大,还是直接偷第二家的收益大。

为了能让整个数组套用上面的动态转移方程,当我们遍历时,i得从2开始,那这就有个问题,假设我们数组一共就2位[1,4],我们定义一个dp数组也是2位,很明显当i=1时,i-2越界了。

没关系,我们可以故意让dp数组多一位,目的就是为了解决这个越界问题,比如这样:

为了套用动态转移方程,我们初始化了dp[0]为0,当偷到第二家时,其实就是求dp[i] = Math.max(1,0 + 4),4更大。虽然有点魔幻,但找出动态转移方程,套用公式,这就是动态问题的一般解决思路。

让我们实现这段代码:

/**
* @param {number[]} nums
* @return {number}
*/
let rob = function (nums) {
let n = nums.length;
if (n === 0) {
return 0;
};
if (n === 1) {
return nums[0];
};
// 在nums长度基础上加1,因为我们要预设一个0便于套公式
let dp = new Array(n + 1);
dp[0] = 0;
dp[1] = nums[0];
// i从2开始套公式
for (let i = 2; i <= n; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);
};
return dp[n];
};

需要注意的有两个点是,第一个是i<=n而不是i<n,原因很简单,比如我们传入[1,4],i一开始就是2,2<2不满足,我们都无法求出偷第二家收益是多少。第二个点是,我们最终加上的是[nums[i-1]],因为i=2其实是站在dp数组的角度多加了一位,对于nums自身而言,下标最大才是1,所以需要减去1才是正确的对应关系。

我们在前面说,因为套用公式为了满足i-2,所以i从2开始,其实还有另一种做法,比如当i=1时,其实是在偷第二家,假设数组为[1,4],此时就是在区分到底是偷第一家划算还是第二家划算,所以我们可以在i<2之前专门做额外的处理,当i超过2之后再套用公式,比如这样:

    let n = nums.length;
if (n === 0) {
return 0;
};
if (n === 1) {
return nums[0];
};
// 因为对于i<2做了额外处理,这里就不额外创建空间了
let dp = new Array(n);
// dp数组直接与nums对齐
dp[0] = nums[0];
for (let i = 1; i < n; i++) {
if (i < 2) {
// 小于2,那就是第一家和第二家比较收益
dp[i] = Math.max(dp[i - 1], nums[i]);
} else {
// 超过2家,套公式
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
};
// 这里是dp的最后一位,所以是长度-1
return dp[n - 1];

思路其实完全相同,只是对于2的处理方式不同,那么本文就到这里了。

JS Leetcode 198. 打家劫舍 题解分析,再次感受动态规划的魅力的更多相关文章

  1. [LeetCode] 198. 打家劫舍II ☆☆☆(动态规划)

    描述 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金.这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的.同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的 ...

  2. LeetCode 198. 打家劫舍(House Robber) 5

    198. 打家劫舍 198. House Robber 题目描述 你是一个专业的小偷,计划偷窃沿街的房屋.每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两 ...

  3. [LeetCode] 198. 打家劫舍 ☆(动态规划)

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

  4. Java实现 LeetCode 198 打家劫舍

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

  5. [LeetCode]198. 打家劫舍(DP)

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

  6. leetcode 198打家劫舍

    讲解视频见刘宇波leetcode动态规划第三个视频 记忆化搜索代码: #include <bits/stdc++.h> using namespace std; class Solutio ...

  7. Leetcode——198. 打家劫舍

    题目描述:题目链接 这道题目也是一道动态规划的题目: 分析一道动态规划的题目可以将解决问题的思路分为下面三个部分: 1:问题的描述.可以定义数组d[ i ] 用于表示第i -1家可以获得的最大金额. ...

  8. leetcode 198 打家劫舍 Python 动态规划

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

  9. LeetCode 198. 打家劫舍(House Robber)LeetCode 213. 打家劫舍 II(House Robber II)

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

  10. 力扣Leetcode 198. 打家劫舍

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

随机推荐

  1. java基础-java面向对象-02-day09

    目录 1. 封装 2. 继承 2.1 什么是方法的重写 2.2 super 2.3 object详解 2.4 equals方法 3. 多态 4. final修饰符 5.抽象类 6. 接口 7. 内部类 ...

  2. 操作系统OS学习总结

    操作系统OS笔记 操作系统概述 操作系统定义 操作系统,是计算机系统中最基本.最重要的系统软件,是其它软件的支撑.控制和管理计算机系统的硬件和软件资源,合理的组织计算机工作流程,并为用户使用计算机提供 ...

  3. Android——共享参数SharedPreferences

    4数据存储 共享参数SharedPreferences.数据库SQLite.SD卡文件.App的全局内存 4.1共享参数SharedPreferences SharedPreferences是一个轻量 ...

  4. 面试官:小伙子来说一说Java中final关键字,以及它和finally、finalize()有什么区别?

    写在开头 面试官:"小伙子,用过final关键字吗?" 我:"必须用过呀" 面试官:"好,那来说一说你对这个关键字的理解吧,再说一说它与finally ...

  5. TLS 加密套件的学习与了解

    TLS 加密套件的学习与了解 加密套件 什么是加密套件? 加密套件是用于在SSL / TLS握手期间协商安全设置的算法的组合. 在ClientHello和ServerHello消息交换之后,客户端发送 ...

  6. Harbor镜像仓库的导出与整理之二

    Harbor镜像仓库的导出与整理之二 背景 前几天参照大神的blog进行了一下harbor的镜像列表的获取与下载. 当时发现一个很诡异的问题. 实际上镜像仓库里面的镜像很多. 但是导出和列表里面的却很 ...

  7. [转帖]Linux字符截取命令-cut

    概述 cut是一个选取命令,.一般来说,选取信息通常是针对"行"来进行分析的,并不是整篇信息分析的. 语法 cut [-bn] [file] 1 或 cut [-c] [file] ...

  8. [转帖]探索惊群 ⑥ - nginx - reuseport

    https://wenfh2020.com/2021/10/12/thundering-herd-tcp-reuseport/   SO_REUSEPORT (reuseport) 是网络的一个选项设 ...

  9. 行云部署成长之路--慢SQL优化之旅 | 京东云技术团队

    ​ 当项目的SQL查询慢得像蜗牛爬行时,用户的耐心也在一点点被消耗,作为研发,我们可不想看到这样的事.这篇文章将结合行云部署项目的实践经验,带你走进SQL优化的奇妙世界,一起探索如何让那些龟速的查询飞 ...

  10. docker上部署启动RabbitMQ

    在docker上部署启动RabbitMQ及使用 一.docker上部署启动RabbitMQ 1.查询rabbitmq镜像 docker search rabbitmq:management 2.拉取r ...