算法日记24:leetcode198打家劫舍(DFS->记忆化搜索->倒序动态规划->循序动态规划)

news/2025/2/23 19:34:34

在这里插入图片描述

一、递归写法(dfs深搜)

1.1)思路讲解

  1. 递归思想
    • dfs(x)表示从第x家店开始的最大劫掠值。
    • 对每一家店铺,有两个选择:
      1. 不劫掠 当前店铺,即跳到下家 dfs(x+1)
      2. 劫掠 当前店铺,且跳过下家(即跳到 dfs(x+2)),并加上当前店铺的价值 home[x]
    • 返回这两者中的最大值,即 max(dfs(x + 1), dfs(x + 2) + home[x])
  2. 基础案例
    • x > n 时,返回0,表示没有店铺可劫掠。

缺点:

  • 重复计算:对于相同的状态,dfs 可能会重复计算,从而导致性能低下,时间复杂度为 O(2^n)

1.2)递归搜索树

在这里插入图片描述

1.3)疑难点解析:[1][4]的洗劫

1、假设在这样的样例中,我们需要[1][4]才是最优情况,

在这里插入图片描述

2、我们可能会考虑,以上我们都是三家三家店铺的考虑,是否会漏掉这种情况呢?实际是不会的

3、因为会在图中的绿色情况达到情况,也就是([1]选择,[3]不选择,[4]选择)的情况下达成[1][4]的效果

在这里插入图片描述

1.4)代码解析

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int>PII;

const int N = 507;
int home[N],mem[N];
int n;

int dfs(int x)
{
    if (x > n) return 0;    

    return max(dfs(x + 1), dfs(x + 2) + home[x]);   //不选/选
}

void solve()
{
    memset(home, 0, sizeof(home)); //因为有多组测试样例,所以每一轮都需要初始化操作
     cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> home[i];
    }
    int res=dfs(1);
    cout << res << '\n';
}


int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int _ =1; cin >> _;
    while (_--) solve();

    system("pause");
    return 0;
}

二、记忆化搜索

2.1)思路讲解

  • 对于已经计算过了的数据不做重复计算,如图中计算了两次店铺4的价值,造成了代码累赘,完全可以用一个记忆化数组mem来存储
  • 由于没有return函数直接返回,所以需要用条件判断把语句关联在一起,防止越界
  1. 记忆化搜索:

    • 我们在递归过程中使用了一个 mem 数组来记录已经计算过的状态。

    • 在递归之前,先检查 mem[x]是否已经计算过:

      • 如果计算过,直接返回 mem[x] 的值,避免重复计算。
      • 如果没有计算过,则继续递归计算,并将结果存入 mem[x]
  2. 优化效果

    • 通过缓存计算结果,减少了大量的重复计算,使得时间复杂度从 O(2^n) 降低到了 O(n)

2.2)代码解析

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int>PII;

const int N = 507;
int home[N],mem[N];
int n;

int dfs(int x)
{
    if (mem[x]) return mem[x];  //记忆化搜索数组

    int sum = 0;

    if (x > n) sum=0;    //注意:由于没有return函数直接返回,所以需要用条件判断把语句关联在一起,防止越界
    else  sum=max(dfs(x + 1), dfs(x + 2) + home[x]);   //不选/选
    
    mem[x] = sum;
    return sum;
}

void solve()
{
    memset(home, 0, sizeof(home)); //因为有多组测试样例,所以每一轮都需要初始化操作
     cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> home[i];
    }
    int res=dfs(1);
    
    cout << res << '\n';
}


int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int _ =1; cin >> _;
    while (_--) solve();

    system("pause");
    return 0;
}

三、动态规划(dp)–>倒序

1.1)思路讲解

  1. 动态规划

    • 采用倒序遍历的方式,从后往前进行状态转移,逐步推导出每一家的最优劫掠方案。

    • 状态转移公式:

      dp[i] = max(dp[i + 1], dp[i + 2] + home[i])
      
      1. dp[i + 1]:表示不选择当前店铺,转移到下一个店铺。
      2. dp[i + 2] + home[i]:表示选择当前店铺,跳过下一个店铺,转移到第 i+2 家店铺。
  2. 时间复杂度

    • 时间复杂度为 O(n),比递归方式更高效。
  3. 空间复杂度

    • 使用了一个 dp 数组,空间复杂度为 O(n)

PS:应该于dfs的公式一致

  • 由于到递归中,只有归的操作才是产生答案的操作,那么我们是否可以仅仅对归进行操作,从而得出答案呢?–>于是,dp产生了

  • 相当于我们从边界往回进行倒序遍历,从已知推未知,所以应该是
    for (int i = n; i >= 1; i--)

  • 注意:p[i]表示从n-i的最大店铺的值,所以应该输出dp[1]


1.2)代码解析

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int>PII;

const int N = 507;
int home[N],mem[N],dp[N];
int n;

int dfs(int x)
{
    if (mem[x]) return mem[x];  //记忆化搜索数组

    int sum = 0;

    if (x > n) sum=0;    //注意:由于没有return函数直接返回,所以需要用条件判断把语句关联在一起,防止越界
    else  sum=max(dfs(x + 1), dfs(x + 2) + home[x]);   //不选/选
    
    mem[x] = sum;
    return sum;
}

void solve()
{
    memset(home, 0, sizeof(home)); //因为有多组测试样例,所以每一轮都需要初始化操作
     cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> home[i];
    }
    for (int i = n; i >= 1; i--)
    {
        dp[i] = max(dp[i + 1], dp[i + 2] + home[i]);
    }
    cout << dp[1];  //表示从n-i的最大店铺的值,所以应该输出dp[1]
}


int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int _ =1; cin >> _;
    while (_--) solve();

    system("pause");
    return 0;
}

四、动态规划(正序)

4.1)思路讲解

  1. 正序 DP
    • dp[i] = max(dp[i - 1], dp[i - 2] + home[i])):表示从前往后依次更新每一家的最优值。
    • 这个实现和倒序方法类似,只是状态更新的顺序不同。
  2. 输出
    • 输出的是 dp[n],表示所有店铺经过劫掠后的最大总值。

  • 那么倒序看起来奇奇怪怪的,我们是否可以正序呢??答案是肯定的,此时我们的搜索方向与原来的恰恰相反,也就是从n开始搜索
  • 注意这道题还有一个映射的思想,因为i-2会使得数组下标越界
	for (int i = 1; i<=n; i++)
    {
        //dp[i] = max(dp[i - 1], dp[i - 2] + home[i]);
        dp[i+2] = max(dp[i+1], dp[i] + home[i]);
    }
    cout << dp[n+2];  //表示从1-n的最大店铺的值,所以应该输出dp[n]
  • 注意答案也要同步映射!!

4.2)递归搜索树

在这里插入图片描述

4.3)代码解析:

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int>PII;

const int N = 507;
int home[N],mem[N],dp[N];
int n;

int dfs(int x)
{
    if (mem[x]) return mem[x];  //记忆化搜索数组

    int sum = 0;

    if (x > n) sum=0;    //注意:由于没有return函数直接返回,所以需要用条件判断把语句关联在一起,防止越界
    else  sum=max(dfs(x + 1), dfs(x + 2) + home[x]);   //不选/选
    
    mem[x] = sum;
    return sum;
}

void solve()
{
    memset(home, 0, sizeof(home)); //因为有多组测试样例,所以每一轮都需要初始化操作
     cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> home[i];
    }
    for (int i = 1; i<=n; i++)
    {
        //dp[i] = max(dp[i - 1], dp[i - 2] + home[i]);
        dp[i+2] = max(dp[i+1], dp[i] + home[i]);
    }
    cout << dp[n+2];  //表示从1-n的最大店铺的值,所以应该输出dp[n]
}


int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int _ =1; cin >> _;
    while (_--) solve();

    system("pause");
    return 0;
}
``


http://www.niftyadmin.cn/n/5863707.html

相关文章

《深度学习实战》第1集:深度学习基础回顾与框架选择

本专栏系列博文旨在帮助读者从深度学习的基础知识逐步进阶到前沿技术&#xff0c;涵盖理论、实战和行业应用。每集聚焦一个核心知识点&#xff0c;并结合实际项目进行实践&#xff0c;避免空谈理论&#xff0c;简洁明快&#xff0c;快速切入代码&#xff0c;所有代码都经过验证…

CSS `transform` 属性详解:打造视觉效果与动画的利器

CSS transform 属性详解&#xff1a;打造视觉效果与动画的利器 引言一、transform 属性简介二、平移&#xff08;Translation&#xff09;三、旋转&#xff08;Rotation&#xff09;四、缩放&#xff08;Scale&#xff09;五、倾斜&#xff08;Skew&#xff09;六、组合变换&am…

《微软量子芯片:开启量子计算新纪元》:此文为AI自动生成

量子计算的神秘面纱 在科技飞速发展的今天,量子计算作为前沿领域,正逐渐走进大众的视野。它宛如一把神秘的钥匙,有望开启未来科技变革的大门,而微软量子芯片则是这把钥匙上一颗璀璨的明珠。 量子计算,简单来说,是一种遵循量子力学规律调控量子信息单元进行计算的新型计算…

破解Docker镜像拉取难题:为Docker配置代理加速镜像拉取

为Docker配置代理加速镜像拉取 概述守护进程配置&#xff08;推荐长期使用&#xff09;Systemd环境变量配置&#xff08;适合临时调整&#xff09;其他 概述 为什么需要配置代理与镜像加速? 跨国网络限制&#xff1a;境外镜像仓库拉取速度慢或无法访问企业安全策略&#xff…

计算机专业知识【揭开汇编的神秘面纱:从基础概念到实际应用】

在计算机编程的广阔领域中&#xff0c;汇编语言扮演着独特而重要的角色。对于刚接触编程的小白来说&#xff0c;汇编可能听起来有些神秘。下面我们就来全面了解一下汇编是什么。 一、汇编的基本定义 汇编语言&#xff08;Assembly Language&#xff09;是一种低级程序设计语言…

Pytorch实现之GIEGAN(生成器信息增强GAN)训练自己的数据集

简介 简介:在训练数据样本之前首先利用VAE来推断潜在空间中不同类的分布,用于后续的训练,并使用它来初始化GAN。与ACGAN和BAGAN不同的是,提出的GIEGAN有一个分类器结构,这个分类器主要判断生成的图像或者样本图像属于哪个类,而鉴别器仅判断图像是来自于生成器还是真实样…

使用 C++ 和 gRPC 的常见陷阱及解决方案

文章目录 1. 环境配置的陷阱1.1 依赖版本冲突或混淆1.2 gRPC 工具缺失 2. 编译和链接的陷阱2.1 运行时库不匹配&#xff08;/MT vs /MD&#xff09;2.2 未解析的外部符号 3. Protobuf 文件生成的陷阱3.1 工具版本不匹配3.2 生成文件运行时库不一致 4. 运行时的陷阱4.1 缺少 DLL…

Maven 基础环境搭建与配置(一)

一、Maven 初印象 在 Java 开发的广袤天地里&#xff0c;Maven 就像是一位神通广大的 “大管家”&#xff0c;为开发者们排忧解难&#xff0c;让项目管理与构建变得轻松高效。它是一个强大的项目管理和构建自动化工具&#xff0c;基于项目对象模型&#xff08;POM&#xff09;…