news 2026/4/19 10:16:44

LeetCode 198. 打家劫舍:动态规划入门经典题详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 198. 打家劫舍:动态规划入门经典题详解

作为动态规划领域最经典的入门题目之一,LeetCode 198. 打家劫舍不仅考察对「状态定义」和「递推逻辑」的理解,更能帮我们建立解决“选或不选”类问题的核心思维。今天就带大家一步步拆解这道题,从题目分析到代码实现,吃透每一个细节,新手也能轻松掌握。

一、题目解读:读懂约束,明确目标

题目核心场景很简单:沿街有若干房屋,每间房有一定现金,相邻房屋不能同时偷窃(否则触发警报),给定房屋现金数组nums,求一夜能偷窃到的最高金额。

关键约束拆解:

  • 约束:相邻房屋互斥,选了第i间,就不能选第i-1间;不选第i间,可选择第i-1间(或不选)。

  • 目标:求“最大金额”,属于「最优化问题」——这类问题往往适合用动态规划求解,通过记录中间状态,避免重复计算。

  • 边界情况:数组为空(无房屋)、数组长度为1(只有一间房),需单独处理。

二、动态规划思路:从“状态”到“递推”

动态规划的核心是「定义状态」和「找到递推公式」,我们一步步推导:

1. 定义DP状态

我们定义dp[i]表示:偷窃前i+1间房屋(即下标0到i的房屋),能获得的最高金额。

为什么这么定义?因为每一步的决策(偷或不偷第i间房),都依赖于前一步的结果,用dp[i]记录前i+1间的最优解,能自然衔接后续的递推。

2. 推导递推公式

对于第i间房屋,我们有两种选择:偷,或者不偷。

  • 选择1:偷第i间房。那么第i-1间房不能偷,此时最高金额 = 前i-1间房的最优解(dp[i-2]) + 第i间房的现金(nums[i])。

  • 选择2:不偷第i间房。那么最高金额 = 前i间房的最优解(dp[i-1])(相当于直接继承前i间的最好结果)。

我们要的是“最高金额”,所以取两种选择的最大值,递推公式如下:

dp[i]=max(dp[i−2]+nums[i],dp[i−1])dp[i] = max(dp[i-2] + nums[i], dp[i-1])dp[i]=max(dp[i2]+nums[i],dp[i1])

3. 初始化边界状态

递推公式需要dp[i-2]和dp[i-1],所以我们需要先初始化前两个状态:

  • 当只有1间房(i=0):dp[0] = nums[0](只能偷这一间)。

  • 当有2间房(i=1):dp[1] = max(nums[0], nums[1])(选现金多的那一间偷)。

4. 遍历顺序

从i=2开始,依次遍历到数组末尾(i = size-1),因为每一个dp[i]都依赖于前面的dp[i-1]和dp[i-2],顺序遍历才能保证我们用到的状态都是已经计算好的。

三、代码逐行解析:把思路落地

给定的代码已经完美实现了上述思路,我们逐行拆解,看懂每一步的作用:

functionrob(nums:number[]):number{// 边界情况1:没有房子,返回0if(nums.length===0)return0;constsize=nums.length;// 边界情况2:只有一间房,返回该房的现金if(size===1)returnnums[0];// 初始化dp数组,存储前i+1间房的最高偷窃金额constdp:number[]=newArray(size);dp[0]=nums[0];// 前1间房(下标0)的最优解dp[1]=Math.max(nums[0],nums[1]);// 前2间房的最优解// 递推:从第3间房(i=2)开始,直到最后一间房for(leti=2;i<size;i++){dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);}// 返回最后一间房对应的最优解(前size间房的最高金额)returndp[size-1];};

关键代码说明

  • 边界处理:先判断数组为空和长度为1的情况,避免后续数组越界,也符合实际场景。

  • dp数组初始化:长度和nums一致,确保每一间房都有对应的状态记录。

  • 循环递推:i从2开始,逐一计算每一间房的最优解,最终dp[size-1]就是所有房屋的最高偷窃金额。

四、测试案例:验证思路正确性

我们用两个常见测试案例,看看代码是否能正常运行:

案例1:nums = [1,2,3,1]

推导过程:

  • dp[0] = 1

  • dp[1] = max(1,2) = 2

  • dp[2] = max(dp[0]+3, dp[1]) = max(1+3, 2) = 4

  • dp[3] = max(dp[1]+1, dp[2]) = max(2+1, 4) = 4

返回结果:4(正确,偷窃第1间和第3间,1+3=4)。

案例2:nums = [2,7,9,3,1]

推导过程:

  • dp[0] = 2

  • dp[1] = max(2,7) = 7

  • dp[2] = max(2+9,7) = 11

  • dp[3] = max(7+3,11) = 11

  • dp[4] = max(11+1,11) = 12

返回结果:12(正确,偷窃第1间、第3间、第5间,2+9+1=12)。

五、优化方向:空间复杂度优化

上述代码的空间复杂度是O(n)(用到了长度为n的dp数组),但我们观察递推公式发现:dp[i]只依赖于dp[i-1]和dp[i-2],不需要存储整个数组。

可以用两个变量替代dp数组,将空间复杂度优化到O(1),优化后代码如下(供参考):

functionrob(nums:number[]):number{if(nums.length===0)return0;if(nums.length===1)returnnums[0];letprevPrev=nums[0];// 对应dp[i-2]letprev=Math.max(nums[0],nums[1]);// 对应dp[i-1]for(leti=2;i<nums.length;i++){constcurrent=Math.max(prevPrev+nums[i],prev);prevPrev=prev;prev=current;}returnprev;};

优化思路:用prevPrev记录dp[i-2],prev记录dp[i-1],每次循环更新这两个变量,避免存储整个dp数组,效率更高。

六、总结:掌握动态规划的核心思维

打家劫舍这道题的核心,是「用状态记录中间最优解,用递推公式衔接前后决策」。其实很多动态规划题都是这个思路:

  1. 明确问题的约束和目标(相邻不偷,求最大金额);

  2. 定义合适的状态(dp[i]代表前i+1间房的最优解);

  3. 推导递推公式(偷或不偷的两种选择,取最大值);

  4. 初始化边界,顺序遍历计算;

  5. (可选)优化空间复杂度,去掉不必要的存储。

这道题作为动态规划的入门题,难度适中,但能帮我们建立“最优子结构”和“无后效性”的思维——后续遇到类似的“选或不选”“相邻约束”问题(如打家劫舍II、III),都能沿用这个思路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/19 10:08:34

如何5分钟快速上手Onekey:Steam游戏清单一键获取终极指南

如何5分钟快速上手Onekey&#xff1a;Steam游戏清单一键获取终极指南 【免费下载链接】Onekey Onekey Steam Depot Manifest Downloader 项目地址: https://gitcode.com/gh_mirrors/one/Onekey Onekey是一款专为Steam游戏玩家和开发者设计的自动化工具&#xff0c;能够在…

作者头像 李华
网站建设 2026/4/19 10:08:30

终极英雄联盟皮肤更换神器:R3nzSkin完整使用指南

终极英雄联盟皮肤更换神器&#xff1a;R3nzSkin完整使用指南 【免费下载链接】R3nzSkin Skin changer for League of Legends (LOL) 项目地址: https://gitcode.com/gh_mirrors/r3/R3nzSkin R3nzSkin是一款专为英雄联盟玩家打造的强大皮肤更换工具&#xff0c;它能够让你…

作者头像 李华
网站建设 2026/4/19 10:08:03

STM32CubeMX + FreeModbus V1.5 保姆级移植教程(含485切换避坑指南)

STM32CubeMX FreeModbus V1.5 工业级移植实战&#xff1a;从零构建稳定RS485从站 最近在帮客户调试一个工业温控项目时&#xff0c;发现不少工程师在移植FreeModbus协议栈时总会在485方向切换和中断处理上栽跟头。记得我第一次用STM32F103做Modbus从站时&#xff0c;就因为没…

作者头像 李华