IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 打家劫舍问题总结 -> 正文阅读

[数据结构与算法]打家劫舍问题总结

1. 打家劫舍Ⅰ

1.1 题目描述

????????你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两件相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
????????给定一个代表房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。

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

来源:力扣(LeetCode)

【提示】

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

1.2 动态规划步骤分析

  • (1)确定状态
    • f[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为f[i]
  • (2)转移方程
    • 决定f[i]的因素就是第i间房屋偷不偷。
      • 若偷:则f[i] = f[i-2] + nums[i];i-1间房屋肯定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为f[i-2]加上第i`房间偷到的钱
      • 若不偷,则f[i] = f[i-1],即考虑i-1房,并不是一定要偷
    • 取两者的最大值即为转移方程:f[i] = max(f[i-1],f[i-2]+nums[i])
  • (3)初始化
    • 由递推公式f[i] = max(f[i-1],f[i-2]+nums[i])知,递推公式的基础是f[0]和f[1]。
从定义上来说
vector<int> f(nums.size());
f[0] = nums[0];
f[1] = max(nums[0],nums[1]);
  • (4)计算顺序
    • 从前到后

1.3 C++实现

int rob(vector<int>& nums){
    if(nums.size() == 0) return 0;
    if(nums.size() == 1) return nums[0];

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

    for(int i = 2;i < nums.size();i++){
        f[i] = max(f[i-1],f[i-2]+nums[i]);
    }

    return f[nums.size()-1];
}

测试

#include <iostream>
#include <vector>
using namespace std;

int rob(vector<int>& nums);

int main(){
    vector<int> nums = {2,7,9,3,1};
    cout << rob(nums) << endl;
    return 0;
}

2. 打家劫舍Ⅱ

2.1 题目描述

????????你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

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

【示例1】

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2, 因为他们是相邻的。

【示例2】

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4

来源:力扣(LeetCode)

2.2 动态规划步骤分析

对于一个数组,成环的话主要有如下两种情况,分别计算两种情况,然后取最大值即可。步骤分析同上。

在这里插入图片描述

2.3 C++实现

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> f(nums.size());
    f[start] = nums[start];
    f[start + 1] = max(nums[start],nums[start + 1]);
    for(int i = start + 2;i <= end;i++){
        f[i] = max(f[i-1],f[i-2]+nums[i]);
    }
    return f[end];
}

测试

#include <iostream>
#include <vector>
using namespace std;

int robRange(vector<int>& nums,int start,int end);
int rob(vector<int>& nums);

int main(){
    vector<int> nums = {1,6,1,9,1};
    cout << rob(nums) << endl;
    return 0;
}

3. 打家劫舍Ⅲ

3.1 题目概述

????????在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

????????计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
在这里插入图片描述

3.2 动态规划步骤分析

????????动态规划其实就是使用状态转移容器来记录状态的变化,这里可以使用一个长度为2的数组,记录当前节点偷与不偷所得到的的最大金钱。
(1)确定递归函数的参数和返回值
???????? 这里我们要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组。参数为当前节点,代码如下:

vector<int> robTree(TreeNode* cur) 

????????其实这里的返回数组就是f 数组。所以f数组以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。本题f数组就是一个长度为2的数组!在递归的过程中,系统栈会保存每一层递归的参数。所以长度为2的数组可以标记树中每个节点的状态。

(2)确定终止条件
????????在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回

if (cur == NULL) return vector<int>{0, 0};

(3)确定遍历顺序

  • 首先明确的是使用后序遍历。 因为通过递归函数的返回值来做下一步计算。
  • 通过递归左节点,得到左节点偷与不偷的金钱。
  • 通过递归右节点,得到右节点偷与不偷的金钱
// 下标0:不偷,下标1:偷
vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右

(4)确定单层递归的逻辑

  • 如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0]; (如果对下标含义不理解就在回顾一下f数组的含义)

  • 如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);

  • 最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}

3.3 C++实现

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

int rob(TreeNode* root) {
     vector<int> result = robTree(root);
     return max(result[0], result[1]);
 }
 // 长度为2的数组,0:不偷,1:偷
vector<int> robTree(TreeNode* cur) {
     if (cur == NULL) return vector<int>{0, 0};
     vector<int> left = robTree(cur->left);
     vector<int> right = robTree(cur->right);
     // 偷cur
     int val1 = cur->val + left[0] + right[0];
     // 不偷cur
     int val2 = max(left[0], left[1]) + max(right[0], right[1]);
     return {val2, val1};
 }

参考

代码随想录

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-12 17:47:47  更:2022-03-12 17:48:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 13:39:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码