初识动态规划(由浅入深)

news2024/11/7 9:15:39

🤓 动态规划入门与进阶指南 📘

动态规划(Dynamic Programming, DP)是一种非常经典的📐算法方法,特别适合用来解决那些有大量重复计算的问题🌀。它可以将复杂的问题拆分为小问题🧩,然后一步一步地找到最终答案🎯。本文将介绍动态规划的基本概念💡,讲解它的好处👍,并通过一些例子展示动态规划的用法,还会与简单的暴力求解方法进行对比⚖️。最后,我们会讨论一些更复杂的例子,帮助你更好地理解动态规划。

❓ 什么是动态规划?

动态规划是一种把问题分成更小的子问题🔍,并用这些子问题的答案来解决整个问题的方法。动态规划最常用于具有 重叠子问题♻️最优子结构⭐ 的问题。

  • 重叠子问题♻️:一个大问题可以被分成多个小问题🧩,而且这些小问题会被多次求解。
  • 最优子结构⭐:问题的最优解可以通过其子问题的最优解来构造🛠️。

通过记住每个子问题的答案📝,动态规划可以避免重复计算,显著提高效率⏱️。

在应用动态规划时,我们通常会使用一个状态数组📊来表示每个小问题的解,然后使用状态转移方程来描述如何从这些子问题得到最终答案🏁。动态规划的核心思想是利用之前已经计算出的结果来避免重复工作。

📊 经典实例对比

1️⃣ 斐波那契数列

🔢 问题描述

斐波那契数列的定义是:

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

目标是计算斐波那契数列的第 n 项📍。

💥 暴力递归求解
// 递归暴力求解
// 时间复杂度:O(2^n)
public static int fibonacciRecursive(int n) {
    if (n <= 1) {
        return n;
    }
    return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}

这种暴力方法简单直接🌀,但它的时间复杂度是 O(2^n),因为它会重复计算很多相同的子问题🔄。对于较大的 n 值,这种方法效率非常低⚠️。

🚀 动态规划求解

我们可以使用动态规划,通过记忆化或从底向上的方式来避免重复计算:

// 动态规划求解(自底向上)
// 时间复杂度:O(n),空间复杂度:O(n)
public static int fibonacciDP(int n) {
    if (n <= 1) {
        return n;
    }
    int[] dp = new int[n + 1];
    dp[1] = 1;
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

使用动态规划,我们可以将时间复杂度降低到 O(n),并且通过记忆中间结果来减少重复计算🗂️。虽然空间复杂度是 O(n),但这个方法能显著减少计算时间⏳。

🛠️ 优化后的动态规划解法

我们可以进一步优化空间复杂度,只存储最近的两个值:

// 优化后的动态规划求解
// 时间复杂度:O(n),空间复杂度:O(1)
public static int fibonacciOptimized(int n) {
    if (n <= 1) {
        return n;
    }
    int prev = 0, curr = 1;
    for (int i = 2; i <= n; i++) {
        int next = prev + curr;
        prev = curr;
        curr = next;
    }
    return curr;
}

在这个实现中,我们将空间复杂度降低到了 O(1)。这种方法的核心是每次只保留最近的两个斐波那契数,因此大大节省了内存💾。

2️⃣ 0/1 背包问题 🎒

❓ 问题描述

有 n 个物品,每个物品有一定的重量和价值⚖️💰,背包的最大容量是 W。目标是在不超过容量的情况下,使背包内的物品总价值最大化💵。

  • 重量数组:weights = [2, 3, 4, 5]
  • 价值数组:values = [3, 4, 5, 6]
  • 背包容量:W = 5
💥 暴力求解

暴力求解就是穷举所有可能的组合🔢,找到最大价值💰。时间复杂度为 O(2^n),因为每个物品都有 “选择” 或 “不选择” 两种可能。这种方法在物品数量多时非常低效⚠️。

🚀 动态规划求解

我们可以用动态规划,通过创建一个二维数组📊来存储子问题的解。状态转移方程为:

  • dp[i][w] 表示前 i 个物品在容量为 w 时的最大价值。
  • 转移方程为:
    • 如果不选第 i 个物品:dp[i][w] = dp[i-1][w]
    • 如果选第 i 个物品:dp[i][w] = max(dp[i-1][w], dp[i-1][w - weights[i-1]] + values[i-1])
// 动态规划求解 0/1 背包问题
// 时间复杂度:O(n * W),空间复杂度:O(n * W)
public static int knapsack(int[] weights, int[] values, int W) {
    int n = weights.length;
    int[][] dp = new int[n + 1][W + 1];
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= W; w++) {
            if (weights[i - 1] <= w) {
                dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1]);
            } else {
                dp[i][w] = dp[i - 1][w];
            }
        }
    }
    return dp[n][W];
}

时间和空间复杂度都是 O(n * W)。动态规划避免了重复计算,从而显著提高了求解效率⏳。

🛠️ 优化空间复杂度

我们可以将二维数组优化为一维数组,只需记录前一行的数据:

// 优化后的动态规划求解 0/1 背包问题
// 时间复杂度:O(n * W),空间复杂度:O(W)
public static int knapsackOptimized(int[] weights, int[] values, int W) {
    int n = weights.length;
    int[] dp = new int[W + 1];
    for (int i = 0; i < n; i++) {
        for (int w = W; w >= weights[i]; w--) {
            dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
        }
    }
    return dp[W];
}

通过使用一维数组,空间复杂度可以降低到 O(W),这使得动态规划在处理大规模的背包问题时更有效🔋。
至于为什么要反向遍历呢?

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

3️⃣ 编辑距离问题 ✍️

❓ 问题描述

编辑距离是指把一个字符串转换为另一个字符串所需的最少操作次数🔄。允许的操作包括插入、删除和替换字符。比如,把 “kitten” 变成 “sitting” 需要 3 次操作。

🚀 动态规划求解

我们可以使用动态规划来解决这个问题。dp[i][j] 表示把 word1[0:i] 变成 word2[0:j] 需要的最小操作次数🔢。

状态转移方程为:

  • 如果 word1[i-1] == word2[j-1],则 dp[i][j] = dp[i-1][j-1]
  • 否则,dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 1)
// 编辑距离的动态规划解法
// 时间复杂度:O(m * n),空间复杂度:O(m * n)
public static int editDistance(String word1, String word2) {
    int m = word1.length();
    int n = word2.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= n; j++) {
            if (i == 0) {
                dp[i][j] = j;
            } else if (j == 0) {
                dp[i][j] = i;
            } else if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = 1 + Math.min(dp[i - 1][j], Math.min(dp[i][j - 1], dp[i - 1][j - 1]));
            }
        }
    }
    return dp[m][n];
}

📚 更多经典动态规划案例

4️⃣ 最长公共子序列(LCS)🔗

最长公共子序列是在两个字符串中同时出现的最长子序列。我们可以用动态规划解决,通过定义 dp[i][j] 表示 text1[0:i]text2[0:j] 的最长公共子序列的长度。

5️⃣ 最长上升子序列(LIS)📈

最长上升子序列的目标是在一个数组中找到最长的子序列,使得子序列中的每个元素都比前一个元素大⬆️。动态规划通过维护一个状态数组来解决。

6️⃣ 矩阵链乘法问题 🔗✖️

矩阵链乘法问题是为了找到一种最优方式来计算一系列矩阵的乘法次数,使得计算量最少。动态规划可以帮助找到这种最优计算方式。

7️⃣ 最小路径和 🛤️

在一个 m x n 的网格中找到从左上角到右下角的最小路径和🚶。动态规划可以通过定义 dp[i][j] 表示到达位置 (i, j) 的最小路径和来解决。

🤔 动态规划的好处总结

  1. 避免重复计算♻️:动态规划通过存储子问题的解来避免重复计算,从而大大降低时间复杂度⏱️。例如,斐波那契数列的暴力求解是 O(2^n),而动态规划可以做到 O(n)

  2. 自底向上的求解方式⬇️⬆️:动态规划通过从小问题到大问题的解决方法,避免了递归的栈开销,减少了栈溢出的风险⚠️。

  3. 适用于重叠子问题和最优子结构的问题💡:很多问题,比如背包问题、编辑距离、最长公共子序列等,都可以用动态规划来高效解决。

  4. 时间与空间的平衡⚖️:动态规划通常需要更多的内存,但通过优化可以降低空间复杂度,例如滚动数组📉。

  5. 适用范围广🌍:动态规划可以用来解决很多不同类型的问题,从简单的数列到复杂的字符串和路径优化问题。

🔚 结论

动态规划是一种非常强大和有效的算法思想,通过记忆中间结果和从小到大的求解方法,它能够大幅度提高解决问题的效率⏳。希望本文能帮助你理解动态规划的基本概念和如何应用💡。接下来你可以试着练习一些经典的动态规划问题,比如最长上升子序列、完全背包、矩阵链乘、编辑距离、最长公共子序列等。这些问题会帮助你更好地掌握动态规划的思想📚。此外,理解动态规划的优化技巧,比如滚动数组和空间压缩,也会对你解决算法问题大有帮助🛠️💪。

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

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

相关文章

【STM32】SD卡

(一)常用卡的认识 在学习这个内容之前&#xff0c;作为生活小白的我对于SD卡、TF卡、SIM卡毫无了解&#xff0c;晕头转向。 SD卡&#xff1a;Secure Digital Card的英文缩写&#xff0c;直译就是“安全数字卡”。一般用于大一些的电子设备比如&#xff1a;电脑、数码相机、AV…

《JVM第5课》虚拟机栈

无痛快速学习入门JVM&#xff0c;欢迎订阅本免费专栏 Java虚拟机栈&#xff08;Java Virtual Machine Stack&#xff0c;简称JVM栈&#xff0c;又称Java方法栈&#xff09;是 JVM 运行时数据区的一部分&#xff0c;主要用于支持Java方法的执行。每当一个新线程被创建时&#xf…

Java Executor RunnableScheduledFuture 总结

前言 相关系列 《Java & Executor & 目录》《Java & Executor & RunnableScheduledFuture & 源码》《Java & Executor & RunnableScheduledFuture & 总结》《Java & Executor & RunnableScheduledFuture & 问题》 涉及内容 《…

软考(中级-软件设计师)数据库篇(1101)

第6章 数据库系统基础知识 一、基本概念 1、数据库 数据库&#xff08;Database &#xff0c;DB&#xff09;是指长期存储在计算机内的、有组织的、可共享的数据集合。数据库中的数据按一定的数据模型组织、描述和存储&#xff0c;具有较小的冗余度、较高的数据独立性和扩展…

zynq PS端跑Linux响应中断

这篇文章主要是讲述如何在Zynq的PS上跑Linux启动IRQ&#xff0c;环境为vivado2019.1&#xff0c;petalinux2019.1 ubuntu20.04&#xff0c;本人初学者&#xff0c;欢迎批评指正 1. Vivado硬件设计 确保自定义IP的中断信号通过 IRQ_F2P 连接到PS端。在开始Petalinux配置之前&a…

R语言贝叶斯

原文链接&#xff1a;R语言贝叶斯进阶&#xff1a;INLA下的贝叶斯回归、生存分析、随机游走、广义可加模型、极端数据的贝叶斯分析https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247625527&idx8&snba4e50376befd94022519152609ee8d0&chksmfa8daad0cdfa…

qt QRadioButton详解

QRadioButton 是一个可以切换选中&#xff08;checked&#xff09;或未选中&#xff08;unchecked&#xff09;状态的选项按钮。单选按钮通常呈现给用户一个“多选一”的选择&#xff0c;即在一组单选按钮中&#xff0c;一次只能选中一个按钮。 重要方法 QRadioButton(QWidget…

webm格式怎么转换成mp4?这9种转换方法你肯定能够学会!

webm格式怎么转换成mp4&#xff1f;WebM&#xff0c;作为一种新兴的视频文件格式&#xff0c;虽然带着革新性的光芒&#xff0c;在视频压缩效率和播放流畅性上表现出色&#xff0c;却也面临着几个重要的挑战&#xff0c;这些问题直接影响了用户的体验&#xff0c;首先&#xff…

HTML 语法规范全解:构建清晰、兼容性强的网页基础

文章目录 一、代码注释1.1 使用注释的主要目的1.2 使用建议二、标签的使用2.1 开始标签和结束标签2.2 自闭合标签2.3 标签的嵌套2.4 标签的有效性三、属性四、缩进与格式4.1 一致的缩进4.2 元素单独占用一行4.3 嵌套元素的缩进4.4 避免冗长的行五、字符编码六、小结在开发 HTML…

10 P1094 [NOIP2007 普及组] 纪念品分组

题目&#xff1a; 代码&#xff1a; #include<iostream> using namespace std; # define M 100 #include<algorithm> int sa[100005];int main() {int w,n;cin>>w>>n;for(int i1;i<n;i){cin>>sa[i];}sort(sa1,sa1n);int l1;int rn;int count…

LeetCode.冗余连接(并查集以及广度优先搜索)

684.冗余连接| 传送门&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 树可以看成是一个连通且 无环 的 无向 图。 给定往一棵 n 个节点 (节点值 1&#xff5e;n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间&#xff0c;且这条附加的边不属于树中…

一文彻底整明白,基于Ollama工具的LLM大语言模型Web可视化对话机器人部署指南

在上一篇博文中&#xff0c;我们在本地部署了Llama 3 8B参数大模型&#xff0c;并用 Python 写了一个控制台对话客户端&#xff0c;基本能愉快的与 Llama 大模型对话聊天了。但控制台总归太技术化&#xff0c;体验不是很友好&#xff0c;我们希望能有个类似 ChatGPT 那样的 Web…

BES2600WM---HiLink RM56 EVK

0 Preface/Foreword 1 环境搭建 1.1 安装依赖工具 sudo apt-get install build-essential gcc g make zlib* libffi-dev e2fsprogs pkg-config flex bison perl bc openssl libssl-dev libelf-dev libc6-dev-amd64 binutils binutils-dev libdwarf-dev u-boot-tools mtd-utils…

Leetcode21:合并两个有效链表

原题地址&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示…

51c嵌入式~IO合集1

我自己的原文哦~ https://blog.51cto.com/whaosoft/12383193 一、单片机通信数据接收解析方法 前阵子一朋友使用单片机与某外设进行通信时&#xff0c;外设返回的是一堆格式如下的数据&#xff1a; AA AA 04 80 02 00 02 7B AA AA 04 80 02 00 08 75 AA AA 04 80 02 00 9B E2…

java或c#是如何对数据库的表字段加密的处理的?

对于表格数据的加密处理&#xff0c;通常涉及到对数据库中存储的数据进行加密&#xff0c;以保护敏感信息。 Java示例&#xff08;使用AES算法加密数据库表数据&#xff09; 首先&#xff0c;你需要一个数据库连接&#xff0c;这里假设你使用的是JDBC连接MySQL数据库。以下是…

Android Studio Dolphin 下载、安装与配置教程

文章目录 Android Studio Dolphin简介一、核心特性二、新增功能三、用户体验优化 一&#xff0c;下载百度网盘迅雷云盘 二&#xff0c;安装三&#xff0c;下载组件四&#xff0c;添加SDK五&#xff0c;创建项目六&#xff0c;安装 Device模拟器运行项目 Android Studio Dolphin…

java开发等一些问题,持续更新

微服务和单服务的区别 微服务&#xff08;Microservices&#xff09;和单体服务&#xff08;Monolithic Architecture&#xff09;是两种不同的软件架构风格&#xff0c;各有其特点和适用场景。 微服务架构&#xff1a; 模块化&#xff1a; 微服务架构将应用程序分解为一系列小…

全国分省灵活就业情况数据集(2015-2019年)

数据简介&#xff1a;就业是民生之本&#xff0c;是“六稳”“六保”之首&#xff0c;对于拥有14亿人口的中国而言&#xff0c;就业问题至关重要。“十四五”规划建议中指出&#xff0c;应强化就业优先政策&#xff0c;千方百计稳定和扩大就业&#xff0c;实现更充分更高质量就…

杂货 | 每日资讯 | 2024.10.31

注意&#xff1a;以下内容皆为AI总结 在2024年10月30日&#xff0c;人工智能领域涌现出一些重要的新发展和大事件。这些新进展涉及人工智能在金融、搜索技术以及大型科技公司业务扩展中的广泛应用。本文将详细解读当天的几大AI事件&#xff0c;以便大家更好地理解当今AI技术的…