动态规划:打家劫舍系列

news2024/9/29 7:26:37

目录

1. 打家劫舍1(线性数组)(LeetCode198)

解法1:动态规划(二维dp数组)  

解法2:动态规划(一维dp数组) 

解法3:动态规划(一维dp数组优化) 

2. 打家劫舍2(环形数组)(LeetCode213)

3. 打家劫舍3(二叉树)(LeetCode337)


1. 打家劫舍1(线性数组)(LeetCode198)

题目描述:https://leetcode.cn/problems/house-robber/description/

  • 题目简述:每间房内都藏有一定的现金(非负整数数组),问一夜之内能够偷窃到的最高金额?
    • 房间造型线性数组
    • 偷窃规则相邻的房间不能同时偷窃
  • 问题分析:当前房屋偷与不偷 取决于 前一个房屋和前两个房屋是否被偷了。可见,当前状态和前面状态会有一种依赖关系,因此可以使用动态规划来求解。

解法1:动态规划(二维dp数组)  

解题思路:动态规划五部曲

  • Step1:确定dp数组(一维)/dp表格(二维)、下标的含义
    • dp[i][0]:第i个房间不被偷,一夜之内能够偷窃到的最高金额为dp[i][0]
    • dp[i][1]:第i个房间被偷,一夜之内能够偷窃到的最高金额为dp[i][1]
    • 每一个房间使用两个状态来表示
  • Step2:确定递推公式
    • dp[i][0] = max(dp[i-1][0], dp[i-1][1])    第i个房间不被偷 → 第i-1个房间可以不被偷,也可以被偷
    • dp[i][1] = dp[i-1][0] + nums[i]           第i个房间被偷 → 第i-1个房间一定不能被偷
  • Step3:初始化dp数组
    • 从递推公式可以看出,i是由i-1推导而来,因此需要初始化第一个元素
    • dp[0][0]:第0个房间不被偷,一夜之内能够偷窃到的最高金额为0
    • dp[0][1]:第0个房间被偷,一夜之内能够偷窃到的最高金额为nums[0]
  • Step4:确定遍历顺序
    • 从递推公式可以看出,i是由i-1推导而来,因此需要从前向后遍历 
  • Step5:打印dp数组(调试)
#include <iostream>
using namespace std;
#include <vector>

class Solution
{
public:
    int rob(vector<int>& nums)
    {
        if (nums.size() == 1) return nums[0];
        if (nums.size() == 2) return max(nums[0], nums[1]);

        // Step3:初始化dp数组
        vector<vector<int>> dp(nums.size(), vector<int>(2, 0));
        dp[0][0] = 0;
        dp[0][1] = nums[0];

        // Step4:确定遍历顺序
        for (int i = 1; i < nums.size(); i++)
        {
            // Step2:确定递推公式
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);  // 不被偷
            dp[i][1] = dp[i - 1][0] + nums[i];           // 被偷
        }
        return max(dp[nums.size() - 1][0], dp[nums.size() - 1][1]);
    }
};

int main()
{
    //vector<int> nums{ 1,2,3,1 };  // 4
    vector<int> nums{ 2,7,9,3,1 };  // 12
    
    Solution s;
    int ans = s.rob(nums);

    cout << ans << endl;

    system("pause");
    return 0;
}

复杂度分析

  • 时间复杂度:O(n)  
  • 空间复杂度:O(n)

解法2:动态规划(一维dp数组) 

解题思路

  • Step1:确定dp数组(一维)/dp表格(二维)、下标的含义
    • dp[i]:考虑[0,i]以内的房屋,一夜之内能够偷窃到的最高金额为dp[i]
    • 注意:此处仅仅是考虑第i个房间,并不一定偷窃第i个房间
  • Step2:确定递推公式
    • dp[i] 可以由以下两个方向推导而来
    • 偷第i个房间dp[i-2] + nums[i]   此时应该考虑i-2及其之前的房间,该区间内的所有房间都有被偷和不被偷的可能(i-1不能考虑)
      • 由于相邻房间不能同时偷窃 且 偷窃第i个房间,因此第i-1个房间一定不能偷窃
      • 考虑[0,i-2]以内的房屋,一夜之内能够偷窃到的最高金额为dp[i-2] 加上 偷窃第i房间的钱
    • 不偷第i个房间dp[i-1]           此时应该考虑i-1及其之前的房间,该区间内的所有房间都有被偷和不被偷的可能
      • 若不偷第i个房间,则第i-1个房间可以被偷,也可以不被偷
      • 考虑[0,i-1]以内的房屋,一夜之内能够偷窃到的最高金额为dp[i-1]
    • dp[i] = max(dp[i-2] + nums[i], dp[i-1])
  • Step3:初始化dp数组
    • 从递推公式可以看出,i是由i-2和i-1推导而来,因此需要初始化前两个元素
    • dp[0]:仅考虑第0个房屋,一夜之内能够偷窃到的最高金额 → 偷窃第0个房间的金额
    • dp[1]:仅考虑第0个和第1个房屋,一夜之内能够偷窃到的最高金额 → 由于相邻房间不能同时偷窃,因此两者只能选其一进行偷窃,因此最高金额为两者的最大值
  • Step4:确定遍历顺序
    • 从递推公式可以看出,i是由i-2和i-1推导而来,因此需要从前向后遍历
  • Step5:打印dp数组(调试)
#include <iostream>
using namespace std;
#include <vector>

class Solution 
{
public:
    int rob(vector<int>& nums) 
    {
        if (nums.size() == 1) return nums[0];
        if (nums.size() == 2) return max(nums[0], nums[1]);

        // Step3:初始化dp数组
        vector<int> dp(nums.size(), 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);

        // Step4:确定遍历顺序
        for (int i = 2; i < nums.size(); i++)
        {
            // Step2:确定递推公式
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[nums.size() - 1];
    }
};

int main()
{
    //vector<int> nums{ 1,2,3,1 };  // 4
    vector<int> nums{ 2,7,9,3,1 };  // 12
    
    Solution s;
    int ans = s.rob(nums);

    cout << ans << endl;

    system("pause");
    return 0;
}

复杂度分析

  • 时间复杂度:O(n)  
  • 空间复杂度:O(n)

解法3:动态规划(一维dp数组优化) 

解题思路:利用"状态压缩"进行优化 → 由于当前数值dp[i]仅依赖于前面两个数值dp[i-1]和dp[i-2],因此只需要维护两个数值 

class Solution
{
public:
    int rob(vector<int>& nums)
    {
        if (nums.size() == 1) return nums[0];
        if (nums.size() == 2) return max(nums[0], nums[1]);

        // Step3:初始化dp数组
        vector<int> dp(2);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);

        // Step4:确定遍历顺序
        for (int i = 2; i < nums.size(); i++)
        {
            // Step2:确定递推公式
            int dpi = max(dp[1], dp[0] + nums[i]);
            dp[0] = dp[1];
            dp[1] = dpi;
        }
        return dp[1];
    }
};

int main()
{
    //vector<int> nums{ 1,2,3,1 };  // 4
    vector<int> nums{ 2,7,9,3,1 };  // 12
    
    Solution s;
    int ans = s.rob(nums);

    cout << ans << endl;

    system("pause");
    return 0;
}

复杂度分析

  • 时间复杂度:O(n)  
  • 空间复杂度O(1)

2. 打家劫舍2(环形数组)(LeetCode213)

题目描述:https://leetcode.cn/problems/house-robber-ii/description/

题目简述:每间房内都藏有一定的现金(非负整数数组),问一夜之内能够偷窃到的最高金额?

  • 房间造型环形数组,首尾两个房间为相邻房间
  • 偷窃规则相邻的房间不能同时偷窃

解题思路:针对首尾房间是否被偷,划分为以下几种情况,每种情况均可以使用"打家劫舍1"的代码求解

  • 首房间和尾房间为相邻房间,因此不能同时被偷(必须限定其中一个不被偷)
  • Case1首房间不被偷,尾房间不做限定  →  考虑[1, nums.size()-1]以内的房屋,一夜之内能够偷窃到的最高金额
  • Case2尾房间不被偷,首房间不做限定  →  考虑[0, nums.size()-2]以内的房屋,一夜之内能够偷窃到的最高金额
#include <iostream>
using namespace std;
#include <vector>

class Solution 
{
private:
    // 该函数的代码与"打家劫舍1"完全相同
    int robRange(vector<int>& nums)
    {
        if (nums.size() == 1) return nums[0];
        if (nums.size() == 2) return max(nums[0], nums[1]);

        vector<int> dp(nums.size(), 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);

        for (int i = 2; i < nums.size(); i++)
        {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[nums.size() - 1];
    }
public:
    int rob(vector<int>& nums) 
    {
        if (nums.size() == 1) return nums[0];
        if (nums.size() == 2) return max(nums[0], nums[1]);

        // Case1:首房间不被偷,尾房间不做限定  →  考虑[1, nums.size()-1]以内的房屋,一夜之内能够偷窃到的最高金额
        vector<int> vec1(nums.begin() + 1, nums.end());
        int result1 = robRange(vec1);
        // Case2:尾房间不被偷,首房间不做限定  →  考虑[0, nums.size()-2]以内的房屋,一夜之内能够偷窃到的最高金额
        vector<int> vec2(nums.begin(), nums.end() - 1);
        int result2 = robRange(vec2);

        return max(result1, result2);
    }
};

int main()
{
    vector<int> nums{ 2,3,2 };  // 3
    //vector<int> nums{ 1,2,3,1 };  // 4

    Solution s;
    int ans = s.rob(nums);

    cout << ans << endl;

    system("pause");
    return 0;
}

小结:环形问题不利于思考,可以将环形展开为线性结构,单独考虑首尾元素是否选取,分情况讨论

3. 打家劫舍3(二叉树)(LeetCode337)

题目描述:https://leetcode.cn/problems/house-robber-iii/description/

题目简述:每间房内都藏有一定的现金(非负整数数组),问一夜之内能够偷窃到的最高金额?

  • 房间造型二叉树
  • 偷窃规则相邻的房间不能同时偷窃 
#include <iostream>
using namespace std;
#include <vector>

struct TreeNode
{
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode() :val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) :val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode* left, TreeNode* right) :val(x), left(left), right(right) {}
};

class Solution
{
private:
    // Step1:确定递归函数的参数和返回值
        // 返回值:长度为2的数组
            // 下标0:该节点的房间不被偷窃,能够盗取的最高金额
            // 下标1:该节点的房间被偷窃,能够盗取的最高金额
    vector<int> traversal(TreeNode* root)
    {
        // Step2:终止条件(初始化dp数组)
            // 对于空节点,无论偷还是不偷都是0
        if (root == NULL) return vector<int>{ 0,0 };

        // Step3:确定单层递归的逻辑(确定递推公式,确定遍历顺序 → 后序遍历)
        vector<int> dp_left = traversal(root->left);    // 左:递归左节点,得到左节点不被偷时所得到的最高金额dp_left[0]、左节点被偷时所得到的最高金额dp_left[1]
        vector<int> dp_right = traversal(root->right);  // 右:递归右节点,得到右节点不被偷时所得到的最高金额dp_right[0]、右节点被偷时所得到的最高金额dp_right[1]
        vector<int> dp(2);                              // 中
        // 当前节点不被偷:左右孩子被偷和不被偷均有可能
        dp[0] = max(dp_left[0], dp_left[1]) + max(dp_right[0], dp_right[1]);
        // 当前节点被偷:左右孩子不可能被偷,只可能是不被偷
        dp[1] = dp_left[0] + dp_right[0] + root->val;

        return vector<int>{dp[0], dp[1]};               // 返回当前节点的状态:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}
    } 
public:
    int rob(TreeNode* root) 
    {
        vector<int> result = traversal(root);
        return max(result[0], result[1]);
    }
};

int main()
{
    // 7
    //TreeNode* node1 = new TreeNode(3);
    //TreeNode* node2 = new TreeNode(2);
    //TreeNode* node3 = new TreeNode(3);
    //TreeNode* node4 = new TreeNode(3);
    //TreeNode* node5 = new TreeNode(1);
    //node1->left = node2;
    //node1->right = node3;
    //node2->right = node4;
    //node3->right = node5;

    // 9
    TreeNode* node1 = new TreeNode(3);
    TreeNode* node2 = new TreeNode(4);
    TreeNode* node3 = new TreeNode(5);
    TreeNode* node4 = new TreeNode(1);
    TreeNode* node5 = new TreeNode(3);
    TreeNode* node6 = new TreeNode(1);
    node1->left = node2;
    node1->right = node3;
    node2->left = node4;
    node2->right = node5;
    node3->right = node6;

    Solution s;
    int ans = s.rob(node1);

    cout << ans << endl;

    system("pause");
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1980461.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信号处理——自相关和互相关分析

1.概括 在信号处理中&#xff0c;自相关和互相关是相关分析非常重要的概念&#xff0c;它们能分析一个信号或两个信号在时间维度的相似性&#xff0c;在振动测试分析、雷达测距和声发射探伤得到了广泛的应用。自相关分析的研究对象为一个信号&#xff0c;互相关分析的研究对象…

spring原理(第八天)

aop的实现原理 AOP 底层实现方式之一是代理&#xff0c;由代理结合通知和目标&#xff0c;提供增强功能 除此以外&#xff0c;aspectj 提供了两种另外的 AOP 底层实现&#xff1a; 第一种是通过 ajc 编译器在编译 class 类文件时&#xff0c;就把通知的增强功能&#xff0c;织…

Linux之文件系统

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 算法 C进阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.磁盘 二.对磁盘进行管理 三.通过inode找到文件…

Netty的几种IO模式的实现与切换

写在文章开头 今天我们就基于Netty来简单聊聊开发中几种常见的IO模式以及Netty对于这几种IO模式的实现&#xff0c;希望对你有帮助。 Hi&#xff0c;我是 sharkChili &#xff0c;是个不断在硬核技术上作死的 java coder &#xff0c;是 CSDN的博客专家 &#xff0c;也是开源项…

如何在RabbitMQ中防止消息丢失

如何在RabbitMQ中防止消息丢失 在分布式系统中&#xff0c;消息的可靠传递是至关重要的。RabbitMQ作为一个强大的消息队列系统&#xff0c;提供了多种机制来确保消息不会丢失。本文将介绍在RabbitMQ中防止消息丢失的几种方法。 消息确认机制 消息发布确认 在RabbitMQ中&…

pdf转换器哪个好?不要错过这4款转换工具

pdf转换器哪个好&#xff1f;选择一款高效的PDF转换器&#xff0c;无疑能极大地便利我们的日常工作与学习。它不仅能够轻松实现PDF文件与Word、Excel、图片等多种格式之间的互转&#xff0c;还支持批量处理&#xff0c;显著提高工作效率。无论是编辑修改、格式调整还是分享传阅…

深入浅出消息队列----【RocketMQ 和 Kafka 消息存储差异对比】

深入浅出消息队列----【RocketMQ 和 Kafka 消息存储差异对比】 RocketMQ 的消息存储Kafka 的消息存储对比 RocketMQ 与 Kafka 本文仅是文章笔记&#xff0c;整理了原文章中重要的知识点、记录了个人的看法 文章来源&#xff1a;编程导航-鱼皮【yes哥深入浅出消息队列专栏】 Roc…

指南!网上卖药品需要什么资质?

随着互联网技术的飞速发展&#xff0c;医药电商已经成为药品和医疗器械销售的重要渠道。处方药的网络销售政策逐步放宽&#xff0c;医药电商行业迎来了快速发展的春天。在这一领域&#xff0c;主要的参与者包括药品销售公司和电商平台。 为了吸引流量和满足处方药审方的需求&a…

第18课 Scratch入门篇:时钟-当前时间

时钟 故事背景&#xff1a; 在一个遥远的科技星球上&#xff0c;时间对于居民们来说无比珍贵。这个星球上的居民们都是技术高手&#xff0c;他们使用先进的编程技术来管理自己的生活。然而&#xff0c;星球上的时间系统最近出现了故障&#xff0c;导致时间的流逝变得不稳定。为…

【终极指南】大模型二次开发:从零基础到高手之路

随着人工智能技术的发展&#xff0c;预训练的大模型&#xff08;例如GPT系列、BERT等&#xff09;已成为自然语言处理领域的关键技术之一。对于开发者来说&#xff0c;掌握如何基于这些大模型进行二次开发&#xff0c;不仅可以提升自身的技术实力&#xff0c;还能为企业带来更多…

Flink 如何处理背压

文章目录 目录 前言 一、什么是背压&#xff1f; 二、处理背压的步骤 1.模拟背压机制 2.为什么要关心背压问题&#xff1f; 总结 前言 初次接触Flink的同学会对背压有很多的疑问。本文就是我学习的一些心得和体会&#xff0c;以及借鉴一些文章的感想。 Flink 如何处理背压效应…

使用snap的安装docker配置阿里云镜像加速

使用snap安装docker非常的简单&#xff0c;一条命令即可 snap install docker 但是通过这个命令安装的docker, 配置阿里云镜像跟常规安装的配置起来不太一样, 下面讲一下配置流程 修改docker配置文件/var/snap/docker/current/config/daemon.json 这个文件应该是已经创建好…

重磅!LangChain 官方发布 Agent IDE!!

1 LangChain 开发现状 LangChain 从应用开发框架出发&#xff0c;提供了一套代码级工具集&#xff0c;旨在降低 LLM 的开发难度&#xff0c;在过去一年中吸引了众多开发者&#xff0c;助力他们迅速打造 AI 大模型应用。然而&#xff0c;还有一群用户&#xff0c;他们希望门槛…

NC 最长无重复子数组

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一个长度…

idea连接oracle

配置 注意&#xff1a; SID指的是实例名称

C语言宠物系统3

在前面的基础上&#xff0c;加上了修改功能和排序功能&#xff0c;可以选择姓名排序&#xff0c;年龄排序&#xff0c;价格排序。 test.c源文件 #include "Pet.h"void menu() {printf("------------------------\n");printf("- 欢迎来到宠物商店 …

实践出真知:Agents 领域“一年打怪升级”的经验分享

编者按&#xff1a;在你构建 AI Agents 时&#xff0c;是否曾遇到这些困扰&#xff1a;总是在简单任务上出错&#xff0c;从而让你有时会怀疑自己的技术水平&#xff1f;面对客户的需求&#xff0c;AI Agent 表现得像个“笨蛋”&#xff0c;无法准确理解和执行指令&#xff1f;…

不同网络上的计算机怎么通信

从 一个网络上计算机的通信 &#xff0c;我们知道&#xff0c;在一个网络里&#xff0c;多台主机通过交换机连接起来&#xff0c;每台主机的网卡有全球唯一的 MAC 地址&#xff0c;一个网络上的主机通过 MAC 地址通信。 那么&#xff0c;多个网络之间如何互联和通信&#xff1…

【轨物方案】智慧供热物联网整体解决方案

目前城市供暖系统当中&#xff0c;供暖设备一直得不到更新和升级&#xff0c;没有合理的监控设备&#xff0c;导致对供暖的合理调控不理想&#xff0c;供暖严重失调而浑然不知&#xff0c;进而出现冷热不均的问题&#xff0c;极易造成资源严重浪费。缺乏成熟的管理系统&#xf…

上门按摩小程序项目开发功能介绍

上门按摩小程序通常设计为连接按摩服务提供者和客户的平台&#xff0c;提供便捷的预约和服务管理功能。以下是这类小程序可能包含的功能&#xff1a; 用户注册和登录&#xff1a; 用户可以注册个人账户并登录&#xff0c;以便管理个人信息和预约记录。 按摩师信息浏览&#xf…