经典算法:打家劫舍(动态规划 + 回溯求最优解)C++ 超详细解析
前言打家劫舍是动态规划的入门经典题核心考察无相邻元素选取的最大和问题。本文将基于 C 实现两种核心解法动态规划空间优化版高效计算最大金额时间 O (n)空间 O (1)回溯法不仅求最大金额还能输出具体偷窃的房屋下标代码完全可直接运行逐行解析逻辑新手也能轻松看懂一、问题描述你是一个专业小偷沿街有若干房屋每间房有固定现金相邻房屋不能同时偷窃否则触发警报。给定非负整数数组表示房屋金额求不触发警报能偷窃的最高金额。示例输入[12,1,3,23]输出35偷窃 1223总和最大二、核心思路1. 动态规划核心公式这是解题的关键对于第i间房屋只有两种选择不偷最大金额 前i-1间的最大值偷最大金额 前i-2间的最大值 当前房屋金额最终状态转移方程dp[i] max(dp[i-1], dp[i-2] nums[i])2. 算法优化基础版用数组dp存储所有状态空间 O (n)优化版只用两个变量保存前两个状态空间 O (1)最优解三、代码实现与逐行解析版本 1动态规划实现推荐包含基础数组版和空间优化版工业级最优写法#include iostream #include vector #include algorithm using namespace std; // 打家劫舍动态规划 基础版使用vector存储dp数组 int rob1(const std::vectorint ar) { int len ar.size(); if (len 0) return 0; // 无房屋收益0 if (len 1) return ar[0];// 只有一间房直接偷 vectorint dp(len 1, 0);// dp[i]表示前i间房的最大收益 dp[0] 0; dp[1] ar[0]; // 第一间房的最大收益 for (int i 2; i len; i) { // 核心公式不偷i / 偷i前i-2间的最大值 dp[i] std::max(dp[i - 1], dp[i - 2] ar[i - 1]); } return dp[len]; } // 动态规划 优化版不使用vector空间O(1) int rob2(const std::vectorint ar) { int len ar.size(); if (len 0) return 0; if (len 1) return ar[0]; // pre dp[i-2], cur dp[i-1]用变量替代数组 int pre 0; int cur ar[0]; for (int i 2; i len; i) { // 计算当前最大值 int tmp std::max(cur, pre ar[i - 1]); // 状态滚动更新 pre cur; cur tmp; } return cur; } int main() { std::vectorint ar1 { 1,2,3,1 }; // 预期4 std::vectorint ar2 { 2,7,9,3,1 }; // 预期12 std::vectorint ar3 { 12,2,3,23 }; // 预期35 // 调用优化版函数 cout rob2(ar1) ( 4 ) endl; cout rob2(ar2) ( 12 ) endl; cout rob2(ar3) ( 35 ) endl; return 0; }代码解析边界处理无房屋 / 一间房直接返回结果避免数组越界基础版rob1dp[i]前i间房屋能偷的最大金额遍历从 2 开始严格套用状态转移方程优化版rob2重点不需要保存所有 dp 值只需要前两个状态pre dp[i-2]cur dp[i-1]每次计算后滚动更新变量空间复杂度从 O (n)→O (1)版本 2回溯法求最大金额 偷窃路径如果需要知道具体偷了哪几间房用回溯法枚举所有合法方案记录最优解#include iostream #include vector #include algorithm using namespace std; class Rob { private: std::vectorint ar; // 房屋金额数组 std::vectorint cur; // 临时标记1偷0不偷 std::vectorint vcur; // 最终最优路径 int len; // 房屋数量 int maxsum; // 最大金额 int cursum; // 当前方案总金额 // 打印数组工具函数 static void PrintVec(const std::vectorint a) { int n a.size(); for (int i 0; i n; i) { printf(%5d, a[i]); } printf(\n----------------------\n); } // 回溯核心函数枚举所有偷窃方案 void MaxRob(int i, int n) { // 递归终止遍历完所有房屋 if (i n) { // 更新最大值和最优路径 if (cursum maxsum) { maxsum cursum; vcur cur; } } else { // 约束前一间没偷才能偷当前间 if (i 0 || cur[i - 1] 0) { cur[i] 1; // 标记偷 cursum ar[i]; // 累加金额 MaxRob(i 1, n); // 递归下一间 cursum - ar[i]; // 回溯撤销选择 cur[i] 0; // 取消标记 } MaxRob(i 1, n); // 不偷当前间直接下一间 } } public: // 构造函数初始化变量 Rob(const std::vectorint nums) { len nums.size(); ar nums; cur.resize(len, 0); maxsum 0; cursum 0; } // 获取最大金额 int maxSum() { if (len 0) return 0; if (len 1) return ar[0]; MaxRob(0, len); return maxsum; } // 打印最优偷窃路径1偷0不偷 void Print() const { for (auto x : vcur) { printf(%5d, x); } printf(\n---------------\n); } }; int main() { std::vectorint ar3 { 12,2,3,23 }; Rob rob(ar3); cout 最大偷窃金额 rob.maxSum() endl; cout 偷窃路径(1偷,0不偷); rob.Print(); return 0; }代码解析类封装把房屋数据、状态、方法封装代码更优雅回溯核心MaxRob约束条件i0第一间或前一间没偷才能偷当前房选择偷 / 不偷递归遍历所有方案回溯撤销选择保证枚举所有可能性结果记录遍历完所有房屋后更新最大金额和最优路径输出不仅返回最大值还能打印哪几间房被偷四、运行结果动态规划版输出4( 4 ) 12( 12 ) 35( 35 )回溯法版输出最大偷窃金额35 偷窃路径(1偷,0不偷) 1 0 0 1✅ 完美匹配预期结果偷第 1 间和第 4 间122335五、两种方案对比方案时间复杂度空间复杂度优点适用场景动态规划 (优化)O(n)O(1)效率极高工业级最优解只需要求最大金额回溯法O(2ⁿ)O(n)可获取具体偷窃路径需要输出最优方案下标总结面试 / 刷题优先写动态规划空间优化版需要路径时用回溯法。六、总结打家劫舍核心是动态规划状态转移方程dp[i] max(dp[i-1], dp[i-2]nums[i])空间优化是面试高频考点用两个变量滚动替代数组回溯法适合需要输出具体方案的场景掌握回溯思想本文代码完整可直接运行适合 C 新手学习动态规划和回溯法建议收藏练习持续分享干货如果对你有帮助欢迎点赞、收藏、关注有问题评论区交流
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2514536.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!