【动态规划专栏】

动态规划基础知识

概念

        动态规划(Dynamic Programming,DP):用来解决最优化问题的算法思想。
        动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化无的艺术。
        一般来说,动态规划将复杂的问题分解为若干子问题,通过综合子问题的最优解来得到原问题的最优解。
        动态规划会将每个求解过的子问题记录下来,这样下次碰到相同的子问题,就可以直接使用之前记录的结果,而不重复计算。

特点

        最优子结构:动态规划将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。(“分”与“合”体现在 状态转移方程)其实有时候用动态规划也不一定就是最优解那种意思。
        重叠子问题:动态规划会将每个求解过的子问题的解记录下来,这样当下一次碰到同样的子问题时,就可以直接使用之前记录的结果,而不是重复计算。(虽然动态规划使用这种方式来提高计算效率,但不能说这种做法就是动态规划的核心)所谓记录就是dp数组。

写法

        递归,自顶向下(Top-down Approach),即从目标问题开始,将它分解成子问题的组合,直到分解至边界为至。
        递推,自底向上(Bottom-up Approach),即从边界开始,不断向上解决问题,直到解决了目标问题;
        适用场景:最大值/最小值, 可不可行, 是不是,方案个数

何时使用动态规划

        一个问题必须拥有重叠子问题和最优子结构,才能使用动态规划去解决。
核心套路:核心就是写出其状态转移方程(穷举);动态规划的本质,是对问题 状态的定义 和 状态转移方程的定义 ( 状态以及状态之间的递推关系 )

        下面给出动态规划中常用到的一些题目,希望能帮助大家成功掌握这门算法技术,分别为斐波那契类型、矩阵类型、动态规划在字符串的应用、最长递增子序列、最长公共子序列、动态规划在树种的应用、背包问题等等,如有错误,欢迎大家指出,谢谢!

        创作不易,点波关注再走呗~~~

斐波那契类型

1.1 爬楼梯(简单70. 爬楼梯)

1.1.1 题目描述

        假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

1.1.2 思路

1、动态规划第一点,先写出动态规划的推导公式

        假设f(x)表示表示爬到第 x级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以可以列举出以下式子

        f(x) = f(x-1)+f(x-2)

2、探索边界条件

        我们是从第 0级开始爬的,所以从第 0级爬到第0级我们可以看作只有一种方案,即 f(0)=1;从第 0级到第1级也只有一种方案,即爬一级,f(1)=1。

1.1.3 复杂度分析

时间复杂度:循环执行 n次,每次花费常数的时间代价,故时间复杂度为 O(n)。
空间复杂度:这里只用了常数个变量作为辅助空间,故空间复杂度为 O(1)。

1.1.4 代码

#include <iostream>
using namespace std;
class Solution {
public:
    int climbStairs(int n) {
        int p = 0, q = 0, r = 1;
        for (int i = 1; i <= n; ++i) {
            p = q; 
            q = r; 
            r = p + q;
        }
        return r;
    }
};
int main(){
    int n = 2;
    int res = Solution().climbStairs(n);
    cout  << res << endl;
}

1.2 斐波那契数(简单509. 斐波那契数)

1.2.1 题目描述

        斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

  • F(0) = 0,F(1) = 1
  • F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n) 。

输入:n=2 输出:1 ;  输入:n=3, 输出 3

1.2.2 思路

1、推导公式题目已经给出,F(n)=F(n-1)+F(n-2)

2、边界条件:F(0)和F(1);

3、此题优化的一个方向:使用滚动数组思想将空间复杂度优化成O(1)

1.2.3 复杂度分析

时间复杂度:O(n)

空间复杂度:O(1)

1.2.4 代码

#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
    // 简单509: 斐波那契数
    int fib(int n) {
        if (n < 2) {
            return n;
        }
        int p = 0, q = 0, r = 1;
        for (int i = 2; i <= n; ++i) {
            p = q; 
            q = r; 
            r = p + q;
        }
        return r;
    }
};
int main()
{
    int n = 3;
    int res = Solution().fib(n);
    cout << res << endl;

    n = 4;
    res = Solution().fib(n);
    cout << res << endl;
}

1.3 打家劫舍(中等198. 打家劫舍)

1.3.1 题目描述

        你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

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

1.3.2 思路

这种比较复杂的动态规划,步骤可能比较麻烦了,可以分为四个解题步骤,如下所示:

  • 定义子问题
  • 写出子问题的递推关系
  • 确定DP数组的计算顺序
  • 空间优化(可选,非必须)

定义子问题

        原问题为从全部房子,将问题缩小为“从k个房间中能偷到的最大金额”,用f(k)表示;子问题包含参数k。假设共有n个房间,则有n个子问题。动态规划实际上就是通过求解一堆子问题的解来获得原问题的解。子问题常需要满足以下条件:

  • 原问题能由子问题表示。
  • 一个字问题的解能够通过其他子问题求解。例如本题中f(k)可以由f(k-1)和f(k-2)求出。

写出子问题的递推关系

        此题中,一共有n个房子,每个房子的金额分别是H0,H1,...,Hn-1,子问题f(k)表示从前k个房子中能偷到的最大金额,那么有两种偷法。

也就是f(k)=max{f(k-1),Hk-1+f(k-2)}; 边界条件为:f(0)=0,f(1)=H0

确定计算顺序

        在确定了子问题的递推关系之后,下一步就是依次计算出这些子问题了。在很多教程中都会写,动态规划有两种计算顺序,一种是自顶向下的、使用备忘录的递归方法,一种是自底向上的、使用 dp 数组的循环方法。不过在普通的动态规划题目中,99% 的情况我们都不需要用到备忘录方法,所以我们最好坚持用自底向上的 dp 数组。

        DP 数组也可以叫”子问题数组”,因为 DP 数组中的每一个元素都对应一个子问题。如下图所示,dp[k] 对应子问题 f(k),即偷前k间房子的最大金额。

        只要搞清楚了子问题的计算顺序,就可以确定 DP 数组的计算顺序。对于小偷问题,我们分析子问题的依赖关系,发现每个 f(k)依赖 f(k−1)和 f(k−2)。也就是说,dp[k] 依赖 dp[k-1] 和 dp[k-2]。

空间优化

空间优化的基本原理是,很多时候我们并不需要始终持有全部的 DP 数组。对于小偷问题,我们发现,最后一步计算 f(n)f(n)f(n) 的时候,实际上只用到了 f(n−1)f(n-1)f(n−1) 和 f(n−2)f(n-2)f(n−2) 的结果。n−3n-3n−3 之前的子问题,实际上早就已经用不到了。那么,我们可以只用两个变量保存两个子问题的结果,就可以依次计算出所有的子问题。

1.3.3 复杂度分析

时间复杂度:O(n)

空间复杂度:O(1)

1.3.4 代码

#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
    int rob(vector<int> &nums)
    {
        int prev = 0;
        int curr = 0;

        // 每次循环,计算“偷到当前房子为止的最大金额”
        for (int i : nums)
        {
            // 循环开始时,curr 表示 dp[k-1],prev 表示 dp[k-2]
            // dp[k] = max{ dp[k-1], dp[k-2] + i }
            int temp = max(curr, prev + i);
            prev = curr;
            curr = temp;
            // 循环结束时,curr 表示 dp[k],prev 表示 dp[k-1]
        }

        return curr;
    }
};
int main()
{
    vector<int> nums = {1, 2, 3, 1};
    int res = Solution().rob(nums);
    cout << res << endl;

    nums = {2, 7, 9, 3, 1};
    res = Solution().rob(nums);
    cout << res << endl;
}

斐波那契类型的动态规划题目练习:

第N个泰波那契数 

删除并获得点数

矩阵类型的动态规划

2.1 中等62. 不同路径

链接:不同路径

2.1.1 题目描述

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?

示例2:

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下

2.1.2 思路

1. 确定dp数组以及下标的含义:dp[i][j] 代表到达矩阵 [i,j] 位置总共具有多少条路径
2. 确定递推公式:到达 [i,j] 的路径总数 dp[i][j] 相当于由 dp[i-1][j]+向下移动一格 和 dp[i][j-1]+向右移动一格 组成。
        dp[i][j] = dp[i−1][j] + dp[i][j−1];
3. dp数组如何初始化:第0行和第0列都赋值为1
4. 确定遍历顺序:从前到后

5. 空间优化:由于dp[i][j]仅与第 i 行和第 i−1 行的状态有关,因此我们可以使用滚动数组代替代码中的二维数组,使空间复杂度降低为 O(n)。

2.1.3 复杂度分析

时间复杂度:O(mn)。

空间复杂度:O(min(m,n)),即为存储所有状态需要的空间。注意到 f(i,j)仅与第 i行和第 i−1 行的状态有关,因此我们可以使用滚动数组代替代码中的二维数组,使空间复杂度降低为 O(n)。此外,由于我们交换行列的值并不会对答案产生影响,因此我们总可以通过交换 m 和 n 使得 m≤n,这样空间复杂度降低至 O(min⁡(m,n))。

2.1.4 代码

class Solution {
public:
    int uniquePaths(int m, int n)
    {
        vector<int> f(n, 1);
        for (int i = 1; i < m; ++i)
        {
            for (int j = 1; j < n; ++j)
            {
                f[j] += f[j - 1];
            }
        }
        return f[n - 1];
    }
};
int main()
{
    cout << Solution().uniquePaths(3, 7) << endl;

    cout << Solution().uniquePaths(3, 2) << endl;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI钦点的“机器人界OpenAI”来了:成立不到两年估值破26亿美元

OpenAI们正在今年因AI而再次火热无比的机器人领域“复刻”一个OpenAI。 2024年2月23日&#xff0c;OpenAI、微软、贝佐斯风投、英伟达等总计18位投资公司向一家机器人公司注资了6.75亿美元&#xff0c;这家公司就是Figure AI。 Figure AI成立于2022年&#xff0c;两年不到经过…

(已解决)Unicode高位码点emoji表情无法解析

问题描述 我在制作游戏论坛项目&#xff0c;希望制作一个表情库&#xff0c;我参考了菜鸟的emoji手册&#xff0c;并且使fromCharCode函数来进行字符串转换&#xff0c;但是经过我的测试&#xff0c;对于超过5位数的高位码点&#xff0c;无法正常解析。 源码&#xff1a; &l…

WiFi模块引领零售数字化转型:智能零售体验再定义

随着科技的不断发展&#xff0c;零售业正迎来一场数字化转型的浪潮。在这个变革过程中&#xff0c;WiFi模块成为零售业中的关键技术&#xff0c;为商家提供了丰富的数字化工具&#xff0c;打造了更智能、便捷、个性化的零售体验。本文将深入探讨WiFi模块在零售数字化转型中的关…

学习使用paddle来构造hrnet网络模型

1、首先阅读了hrnet的网络结构分析&#xff0c;了解到了网络构造如下&#xff1a; 参考博文姿态估计之2D人体姿态估计 - &#xff08;HRNet&#xff09;Deep High-Resolution Representation Learning for Human Pose Estimation&#xff08;多家综合&#xff09;-CSDN博客 最…

Python的With...As 语句:优雅管理资源的技术探索【第116篇—With...As 语句】

Python的With…As 语句&#xff1a;优雅管理资源的技术探索 在Python编程中&#xff0c;with...as语句是一项强大而优雅的功能&#xff0c;用于管理资源&#xff0c;如文件、网络连接、数据库连接等。本文将深入介绍with...as语句的用法、其工作原理&#xff0c;并通过代码示例…

光子嫩肤仪面罩控制器PCB电路中升压恒流芯片FP7208的应用

护肤已经成为现代人日常生活中不可或缺的一部分&#xff0c;尤其对于追求美丽肌肤的人来说&#xff0c;寻找一款适合自己的护肤利器至关重要。 光子嫩肤仪作为一种高科技美容仪器&#xff0c;受到越来越多人的追捧。其中&#xff0c;FP7208LED升压驱动IC作为其核心部件之一&am…

TQ15EG开发板教程:创建运行petalinux2019.1

工程网盘链接&#xff1a;https://pan.baidu.com/s/1vFRpzmbifXt7GypU9aKjeg 提取码&#xff1a;0ylh 首先需要使用与petalinux相同版本的vivado创建工程&#xff0c;与之前不同的是在创建硬件设计时需要勾选上添加bit文件&#xff0c;所以要在生成bit文件之后再创建硬件设计…

谷粒商城【成神路】-【8】——商品上架

目录 1.数据模型封装 1.es数据模型 2.将es数据模型封装为JAVA bean 3.根据前端发送请求,编写controller 2.模型实现 2.1服务controller 2.2服务service 2.3服务远程调用接口 2.4检索服务controller 2.5检索服务保存到es 2.6库存查询服务 1.数据模型封装 1.es数据模…

银河麒麟之Workstation安装

一、VMware Workstation简介 VMware Workstation是一款由VMware公司开发的虚拟化软件&#xff0c;它允许用户在一台物理计算机上运行多个操作系统&#xff0c;并在每个操作系统中运行多个虚拟机。VMware Workstation提供了一个可视化的用户界面&#xff0c;使用户可以轻松创建、…

纵行科技荣登“中国物联网企业投资价值50强”、“中国物联网行业创新产品榜”

近日&#xff0c;由深圳市物联传媒有限公司、AIoT星图研究院、IOTE组委会、深圳市物联网产业协会主办的“2023‘物联之星’中国物联网行业年度榜单”评选结果正式公布。厦门纵行信息科技有限公司&#xff08;以下简称“纵行科技”&#xff09;最终从500多家参评企业中脱颖而出&…

数据库-ODBC操作

一、ODBC 数据源配置 打开ODBC数据源管理器&#xff1a; 在Windows搜索栏中键入“ODBC数据源”并选择“ODBC数据源(64位)”&#xff08;如果你的系统是64位的&#xff09;。如果你的系统是32位的&#xff0c;你可以选择“ODBC数据源(32位)”。或者&#xff0c;你可以在控制面…

使用DockerFile构建Tomcat镜像

1、准备镜像文件tomcat压缩包&#xff0c;jdk的压缩包 tomcat链接&#xff1a;https://pan.baidu.com/s/1Xpecb-BSGR2sdxSL7FDtBw?pwd1234 提取码&#xff1a;1234 jdk链接&#xff1a;https://pan.baidu.com/s/1mQHInn27j1I9uuuicBsyAA?pwd1234 提取码&#xff1a;1234 …

网工学习 DHCP配置-接口模式

网工学习 DHCP配置-接口模式 学习DHCP总是看到&#xff0c;接口模式、全局模式、中继模式。理解起来也不困难&#xff0c;但是自己动手操作起来全是问号。跟着老师视频配置啥问题没有&#xff0c;自己组建网络环境配置就是不通&#xff0c;悲催。今天总结一下我学习接口模式的…

信息系统安全与对抗-作业2

目录 1、使用自己姓名拼音创建一个账户&#xff0c; 并使用命令和图形化查看 2、使用自己拼音打头字母创建一个隐藏账户 &#xff0c;并使用命令和图形化查看 3、使用命令启动 telnet 服务 4、使用命令打开防火墙 23 端口 5、熟悉LINUX系统&#xff0c;使用命令行创建用户…

docker基线安全修复和容器逃逸修复

一、docker安全基线存在的问题和修复建议 1、将容器的根文件系统挂载为只读 修复建议&#xff1a; 添加“ --read-only”标志&#xff0c;以允许将容器的根文件系统挂载为只读。 可以将其与卷结合使用&#xff0c;以强制容器的过程仅写入要保留的位置。 可以使用命令&#x…

不同控制方式下的无人机二维码识别降落对比

无人机技术的快速发展正在推动众多行业的革新&#xff0c;从农业监测、灾害响应到城市规划和物流配送&#xff0c;无人机的应用前景无限广阔。随着应用场景的多样化&#xff0c;无人机精准降落成为一大挑战。基于PX4飞控固件和ROS系统的开源自主无人机平台Prometheus应运而生。…

【Linux】进程间通信之共享内存

文章目录 引入共享内存的原理共享内存的相关接口shmget()shmat()shmdt()shmctl() 共享内存的简单使用共享内存的特点 引入 进程间通信&#xff0c;顾名思义就是一个进程和另一个进程之间进行对话&#xff0c;以此完成数据传输、资源共享、通知事件或进程控制等。 众所周知&am…

Vscode安装,ssh插件与配置

原因 发现很多新人在练习linux&#xff0c;可是只有windows机的时候&#xff0c;一般都是下载虚拟机&#xff0c;然后在虚拟机上安装ubuntu等linux平台。每次需要在linux中写代码&#xff0c;就打开ubuntu&#xff0c;然后在终端上用vim写代码&#xff0c;或者先编辑代码文本&…

hook函数——useReducer

目录 1.useReducer定义2.useReducer用法3.useState和useReducer区别 1.useReducer定义 const [state, dispatch] useReducer(reducer, initialArg, init?) reducer&#xff1a;用于更新 state 的纯函数。参数为 state 和 action&#xff0c;返回值是更新后的 state。state …

JVM相关问题

JVM相关问题 一、Java继承时父子类的初始化顺序是怎样的&#xff1f;二、JVM类加载的双亲委派模型&#xff1f;三、JDK为什么要设计双亲委派模型&#xff0c;有什么好处&#xff1f;四、可以打破JVM双亲委派模型吗&#xff1f;如何打破JVM双亲委派模型&#xff1f;五、什么是内…