【算法与数据结构】198、213、337LeetCode打家劫舍I, II, III

news2024/9/22 11:41:08

文章目录

  • 一、198、打家劫舍
  • 二、213、打家劫舍 II
  • 三、337、打家劫舍III
  • 三、完整代码

所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。

一、198、打家劫舍

在这里插入图片描述
  思路分析:打家劫舍是动态规划的的经典题目。本题的难点在于递归公式和初始化。

  • 第一步, d p [ j ] dp[j] dp[j]的含义。 d p [ j ] dp[j] dp[j]代表到第 j j j家的时候,偷窃到的最高金额。
  • 第二步,递推公式。 d p [ j ] dp[j] dp[j]仅仅与 d p [ j − 1 ] dp[j-1] dp[j1] d p [ j − 2 ] dp[j-2] dp[j2]有关。如果不偷第 j j j家,则偷窃金额不变, d p [ j ] = d p [ j − 1 ] dp[j] = dp[j-1] dp[j]=dp[j1]。如果偷第 j j j家,那么偷窃金额在 d p [ j − 2 ] dp[j-2] dp[j2]基础上加上 n u m s [ i ] nums[i] nums[i],即 d p [ j ] = d p [ j − 2 ] + n u m s [ i ] dp[j] = dp[j-2] + nums[i] dp[j]=dp[j2]+nums[i]。综合二者, d p [ j ] = m a x ( d p [ j − 1 ] , d p [ j − 2 ] + n u m s [ i ] ) dp[j] = max(dp[j-1], dp[j-2] + nums[i]) dp[j]=max(dp[j1],dp[j2]+nums[i])
  • 第三部,元素初始化。 d p [ 0 ] dp[0] dp[0]初始化为0,代表还没开始偷窃; d p [ 1 ] dp[1] dp[1]初始化为 n u m [ 0 ] num[0] num[0]
  • 第四部,递归顺序。循环从 j = 2 j = 2 j=2开始。
  • 第五步,打印结果。
      程序如下
// 198、打家劫舍,动态规划
class Solution {
public:
    int rob(vector<int>& nums) {
        vector<int> dp(nums.size() + 1, 0);
        dp[1] = nums[0];
        for (int i = 2; i <= nums.size(); i++) {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i-1]);
        }
         return dp[nums.size()];
    }
};

复杂度分析:

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

  因为只用到了dp数组的最后一个元素,实际上不需要保存所有的元素。因此对上述代码进行内存优化,将空间复杂度降低到 O ( 1 ) O(1) O(1),但是递归的过程不明显,找bug费劲。

// 198、打家劫舍,动态规划-内存优化
class Solution2 {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        int first = nums[0], second = max(nums[0], nums[1]);
        for (int i = 2; i < nums.size(); i++) {
            int temp = second;
            second = max(second, first + nums[i]);
            first = temp;
        }
        return second;
    }
};

复杂度分析:

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

二、213、打家劫舍 II

在这里插入图片描述

  思路分析:本题是打家劫舍I的升级版,要求第一家和最后一家是连着的,不能同时偷。这是一个非此即彼的问题。要么偷第一家,不偷最后一家,这等于将最后一家排除在外。反之,不偷第一家,偷最后一家,等价于将第一家排除在外。假设第一家的下标为 0 0 0,最后一家的下标为 i − 1 i-1 i1,那么一共有两种情况:偷窃范围 [ 0 , i − 2 ] [0, i - 2] [0,i2],偷窃范围 [ 1 , i − 1 ] [1, i - 1] [1,i1]。然后应用打家劫舍I的思路来做即可。以下是动态规划的代码,内存优化版本就没给出了,思路都是一样的。

  程序如下

// 213、打家劫舍II,动态规划
class Solution3 {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        int result1 = robRange(nums, 0, nums.size() - 2);
        int result2 = robRange(nums, 1, nums.size() - 1);
        return max(result1, result2);
    }
    int robRange(vector<int>& nums, int start, int end) {
        if (end == start) return nums[start];
        vector<int> dp(nums.size(), 0);
        dp[start] = nums[start];
        dp[start + 1] = max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; i++) {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }
        return dp[end];
    }
};

复杂度分析:

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

三、337、打家劫舍III

在这里插入图片描述
在这里插入图片描述

  思路分析:本题是打家劫舍I的变体,原题目中的数组变成了二叉树。本题涉及到树形递归和动态规划,我们就结合递归三部曲和动态规划五步骤:

  • 1、返回值和递归参数。我们需要判断一个节点要不要偷,而偷不偷取决于动作带来的收益。因此,我们需要返回一个节点偷与不偷的两个状态所得的金额。这就是一个长度为2的数组。这里我们假设这个二维数组第一个元素代表不偷的收益,第二个元素代表偷的收益,{ 0 , 1 = 不偷的收益,偷的收益 {0, 1} = {不偷的收益,偷的收益} 0,1=不偷的收益,偷的收益}。输入参数是当前节点。
  • 2、确定终止条件。当遇到空节点就返回,空节点不会带来收益。因此返{ 0 , 0 {0,0} 0,0}。
	if (cur == NULL) return vector<int>{0, 0};
  • 3、确定遍历顺序。因为当前节点偷不偷需要根据左右孩子的返回值来进行判断,所以 我们需要先得到左右孩子的返回值,即先遍历左右孩子。在所有的遍历顺序中,只有后序遍历(左右中遍历顺序)满足。
	vector<int> left = robTree(cur->left); // 左
	vector<int> right = robTree(cur->right); // 右
  • 4、确定单层递归逻辑。对于当前节点来说,只有两个情况。如果偷当前节点,那么左右孩子节点就不能偷,偷的收益=左孩子不偷的收益+右孩子不偷的收益。如果不偷当前节点,那么左右孩子节点可偷可不偷,至于究竟偷不偷就看那个收益大(注意偷的收益未必更大,偷了小的金额,旁边大的金额就偷不了)。不偷的收益 = max(左孩子不偷的收益,左孩子偷的收益)+max(右孩子不偷的收益,右孩子偷的收益)。将文字抽象成公式:
 	int val1 = cur->val + left[0] + right[0];   // 偷当前节点,那么左右孩子节点不能偷
	int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不偷当前节点,那么左右孩子节点可以偷也可以不偷,取决于偷或者是不偷的金额。
  • 5、具体示例推导dp数组,验证。
      程序如下
// 337、打家劫舍III动态规划
class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> result = robTree(root);
        return max(result[0], result[1]);
    }
    vector<int> robTree(TreeNode* cur) {    // 返回一个二维数组, {0, 1} = {不偷的金额,偷的金额}
        if (cur == NULL) return vector<int>{0, 0};
        vector<int> left = robTree(cur->left);
        vector<int> right = robTree(cur->right);
        int val1 = cur->val + left[0] + right[0];   // 偷当前节点,那么左右孩子节点不能偷
        int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不偷当前节点,那么左右孩子节点可以偷也可以不偷,取决于偷或者是不偷的金额。
        return { val2, val1 };
    }
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),每个节点只遍历了一次。
  • 空间复杂度: O ( l o g n ) O(log n) O(logn),算上递推系统栈的空间。

三、完整代码

// 打家劫舍I, II
# include <iostream>
# include <vector>
# include <algorithm>
using namespace std;

// 198、打家劫舍,动态规划
class Solution {
public:
    int rob(vector<int>& nums) {
        vector<int> dp(nums.size() + 1, 0);
        dp[1] = nums[0];
        for (int i = 2; i <= nums.size(); i++) {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i-1]);
        }
         return dp[nums.size()];
    }
};

// 198、打家劫舍,动态规划-内存优化
class Solution2 {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        int first = nums[0], second = max(nums[0], nums[1]);
        for (int i = 2; i < nums.size(); i++) {
            int temp = second;
            second = max(second, first + nums[i]);
            first = temp;
        }
        return second;
    }
};

// 213、打家劫舍II,动态规划
class Solution3 {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        int result1 = robRange(nums, 0, nums.size() - 2);
        int result2 = robRange(nums, 1, nums.size() - 1);
        return max(result1, result2);
    }
    int robRange(vector<int>& nums, int start, int end) {
        if (end == start) return nums[start];
        vector<int> dp(nums.size(), 0);
        dp[start] = nums[start];
        dp[start + 1] = max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; i++) {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }
        return dp[end];
    }
};

int main() {
    vector<int> nums = { 1,2,3,1 };
    Solution3 s1;
    int result = s1.rob(nums);
    cout << result << endl;
    system("pause");
    return 0;
}
// 337、打家劫舍III
# include <iostream>
# include <vector>
# include <string>
# include <queue>
using namespace std;

// 树节点定义
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) {}
};

template<typename T>
void my_print(T& v, const string msg)
{
    cout << msg << endl;
    for (class T::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << ' ';
    }
    cout << endl;
}

template<class T1, class T2>
void my_print2(T1& v, const string str) {
    cout << str << endl;
    for (class T1::iterator vit = v.begin(); vit < v.end(); ++vit) {
        for (class T2::iterator it = (*vit).begin(); it < (*vit).end(); ++it) {
            cout << *it << ' ';
        }
        cout << endl;
    }
}

// 前序遍历迭代法创建二叉树,每次迭代将容器首元素弹出(弹出代码还可以再优化)
void Tree_Generator(vector<string>& t, TreeNode*& node) {
    if (!t.size() || t[0] == "NULL") return;    // 退出条件
    else {
        node = new TreeNode(stoi(t[0].c_str()));    // 中
        if (t.size()) {
            t.assign(t.begin() + 1, t.end());
            Tree_Generator(t, node->left);              // 左
        }
        if (t.size()) {
            t.assign(t.begin() + 1, t.end());
            Tree_Generator(t, node->right);             // 右
        }
    }
}

// 层序遍历
vector<vector<int>> levelOrder(TreeNode* root) {
    queue<TreeNode*> que;
    if (root != NULL) que.push(root);
    vector<vector<int>> result;
    while (!que.empty()) {
        int size = que.size();  // size必须固定, que.size()是不断变化的
        vector<int> vec;
        for (int i = 0; i < size; ++i) {
            TreeNode* node = que.front();
            que.pop();
            vec.push_back(node->val);
            if (node->left) que.push(node->left);
            if (node->right) que.push(node->right);
        }
        result.push_back(vec);
    }
    return result;
}

// 337、打家劫舍III动态规划
class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> result = robTree(root);
        return max(result[0], result[1]);
    }
    vector<int> robTree(TreeNode* cur) {    // 返回一个二维数组, {0, 1} = {不偷的金额,偷的金额}
        if (cur == NULL) return vector<int>{0, 0};
        vector<int> left = robTree(cur->left);
        vector<int> right = robTree(cur->right);
        int val1 = cur->val + left[0] + right[0];   // 偷当前节点,那么左右孩子节点不能偷
        int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不偷当前节点,那么左右孩子节点可以偷也可以不偷,取决于偷或者是不偷的金额。
        return { val2, val1 };
    }
};

int main() {
    vector<string> t = { "3", "2", "NULL", "3", "NULL", "NULL", "3", "NULL", "1", "NULL", "NULL"};   // 前序遍历
    TreeNode* root = new TreeNode();    // 生成根节点
    Tree_Generator(t, root);            // 生成树
    vector<vector<int>> tree = levelOrder(root);    // 层序遍历
    my_print2<vector<vector<int>>, vector<int>>(tree, "目标树:");  // 打印层序遍历

    Solution s1;
    int result = s1.rob(root);
    cout << "最大金额为:" << result << endl;
    system("pause");
    return 0;
}

end

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

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

相关文章

Android开发之UI控件

TextView 实现阴影效果的textview android:shadowColor"#ffff0000" 设置阴影颜色为红色android:shadowRadius"3" 设置阴影的模糊程度为3android:shadowDx"10" 设置阴影在水平方向的偏移android:shadowDy"10" 设置阴影在竖直方向的偏…

iOS17使用safari调试wkwebview

isInspectable配置 之前开发wkwebview的页面的时候一直使用safari调试&#xff0c;毕竟jssdk交互还是要用这个比较方便&#xff0c;虽说用一个脚本插件没问题。不过还是不太方便。 但是这个功能突然到了iOS17之后发现不能用了&#xff0c;还以为又是苹果搞得bug&#xff0c;每…

Unity 状态模式(实例详解)

文章目录 简介示例1&#xff1a;基础角色状态切换示例2&#xff1a;添加更多角色状态示例3&#xff1a;战斗状态示例4&#xff1a;动画同步状态示例5&#xff1a;状态机管理器示例6&#xff1a;状态间转换的条件触发示例7&#xff1a;多态行为与上下文类 简介 Unity 中的状态模…

一个产品是怎么诞生的

一个产品的诞生&#xff0c;首先从假设需求开始&#xff0c;或者从玩耍的创客开始。 假设需求往往风险很大&#xff0c;你如果没有结合实际的生活经验或者是玩耍经验&#xff0c;凭空在脑子里想到一个东西&#xff0c;要把它创造出来&#xff0c;这样的东西极有可能会遭遇商业上…

ASP.NET Core 使用 SignalR 的简单示例

写在前面 ASP.NET SignalR 是一个开源代码库&#xff0c;简化了Web实时通讯方案&#xff0c;可以实时地通过服务端将信息同步推送到各个客户端&#xff0c;可应用于 需要从服务器进行高频更新的应用&#xff1a;包括游戏、社交网络、投票、拍卖、地图和GPS应用&#xff1b; 仪…

Servlet过滤器个监听器

过滤器和监听器 过滤器 什么是过滤器 当浏览器向服务器发送请求的时候&#xff0c;过滤器可以将请求拦截下来&#xff0c;完成一些特殊的功能&#xff0c;比如&#xff1a;编码设置、权限校验、日志记录等。 过滤器执行流程 Filter实例 package com.by.servlet;import jav…

2024年航海制造工程与海洋工程国际会议(ICNMEME2024)

一、【会议简介】 2024年航海制造工程与海洋工程国际会议(ICNMEME2024)旨在将研究人员、工程师、科学家和行业专业人士聚集在一个开放论坛上&#xff0c;展示他们在导航制造工程与海洋工程领域的激励研究和知识转移理念。然而&#xff0c;我们也认识到&#xff0c;工程师的未来…

【操作系统·考研】虚拟内存管理

1.概述 传统存储管理方式具有两个特征 一次性&#xff1a;作业必须一次性全部装入内存后&#xff0c;才能开始运行。驻留性&#xff1a;作业被装入内存后&#xff0c;就一直驻留在内存中&#xff0c;在其运行期间作业的任何部分都无法被换出。 显然&#xff0c;这两个特性非…

【深度学习】数据归一化/标准化 Normalization/Standardization

目录 一、实际问题 二、归一化 Normalization 三、归一化的类型 1. Min-max normalization (Rescaling) 2. Mean normalization 3.Z-score normalization (Standardization) 4.非线性归一化 4-1 对数归一化 4-2 反正切函数归一化 4-3 小数定标标准化&#xff08;Demi…

山海鲸智慧教育方案:教育数据的未来

作为山海鲸可视化软件的开发者&#xff0c;我们深知数据可视化在教育领域的重要价值。山海鲸智慧教育解决方案正是在这样的背景下应运而生&#xff0c;致力于为教育行业提供高效、直观的数据可视化解决方案。 随着教育信息化的深入推进&#xff0c;教育数据呈爆炸式增长。如何…

嵌入式学习 Day14

一. 三个函数 1.strncpy char *strncpy(char *dest, const char *src, size_t n) // 比正常拷贝多了一个n { n < strlen(src) // 只拷贝前n个字符&#xff0c;最终dest中不会有\0 n strlen(src) // 正常拷贝 n > strlen(src) …

【Golang】ModbusRTU协议CRC16校验算法

CRC校验码是通过在数据后面附加一个短的校验序列来生成的&#xff0c;用于检测数据在传输过程中是否发生错误。CRC16是一种特定的CRC校验算法&#xff0c;它生成一个16位的校验码。 下面是使用Go语言实现CRC16校验算法的代码&#xff1a; package main import ("encoding…

【01】Linux 基本操作指令

带⭐的为重要指令 &#x1f308; 01、ls 展示当前目录下所有文件&#x1f308; 02、pwd 显示用户当前所在路径&#x1f308; 03、cd 进入指定目录&#x1f308; 04、touch 新建文件&#x1f308; 05、tree 以树形结构展示所有文件⭐ 06、mkdir 新建目录⭐ 07、rmdir 删除目录⭐…

某赛通电子文档安全管理系统 PolicyAjax SQL注入漏洞复现

0x01 产品简介 某赛通电子文档安全管理系统(简称:CDG)是一款电子文档安全加密软件,该系统利用驱动层透明加密技术,通过对电子文档的加密保护,防止内部员工泄密和外部人员非法窃取企业核心重要数据资产,对电子文档进行全生命周期防护,系统具有透明加密、主动加密、智能…

Linux--redhat9创建软件仓库

1.插入光盘&#xff0c;挂载镜像 模拟插入光盘: 点击:虚拟机-可移动设备-CD/DVD 设备状态全选&#xff0c;使用ISO影响文件选择当前版本镜像&#xff0c;点击确认。 2.输入: df -h 可以显示&#xff0c;默认/dev/sr0文件为光盘文件&#xff0c;挂载点为/run/media/root/镜像…

【操作系统·考研】文件系统基础

1.概述 文件(File)是以硬盘为载体的存储在计算机上的信息集合&#xff0c;文件可以是文本文档、图片、程序等&#xff0c;基本访问单元可以是字节或记录&#xff0c;可以长期储存在硬盘中&#xff0c;并允许可控制的进程间共享访问&#xff0c;还可以被组成成更复杂的结构。 在…

关于 IntelliJ IDEA 中 Schedule for Addition 的问题

IntelliJ IDEA是一款强大的Java集成开发环境&#xff0c;由JetBrAIns公司开发。它以其智能代码编辑、代码分析工具、自动代码补全、强大的调试功能和内建的版本控制等特性而闻名。此外&#xff0c;它还支持Kotlin、Groovy、Scala和Android开发等多种语言和框架。 IntelliJ IDE…

【XR806开发板试用】在 xr806 上用 ncnn 跑神经网络 mnist

在 xr806 上用 ncnn 跑神经网络 mnist 0x0 介绍 xr806 和 ncnn https://xr806.docs.aw-ol.com/ XR806是全志科技旗下子公司广州芯之联研发设计的一款支持WiFi和BLE的高集成度无线MCU芯片&#xff0c;支持鸿蒙L0系统 https://github.com/Tencent/ncnn ncnn是腾讯开源的高性…

纯静态微信小程序水果商城

首页代码 <view class"container"><!-- 轮播图 --><view class"swiper-container"><swiper class"screen-swiper" indicator-dots"true" circular"true" autoplay"true" interval"300…

【开源】基于JAVA+Vue+SpringBoot的数据可视化的智慧河南大屏

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 A4.2 数据模块 B4.3 数据模块 C4.4 数据模块 D4.5 数据模块 E 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数据可视化的智慧河南大屏&#xff0c;包含了GDP、…