Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >常见动态规划类型--案例详解

常见动态规划类型--案例详解

原创
作者头像
刺槐儿
修改于 2023-11-18 13:00:22
修改于 2023-11-18 13:00:22
7600
举报
文章被收录于专栏:技术路线技术路线

在面试中,好多题都会用到动态规划,系统性的学习动态规划,其重要性不言而喻。

动态规划的核心思想是将原问题拆解成子问题,并通过解决子问题来求解原问题。为了避免重复计算,动态规划会将子问题的解进行存储,在需要的时候直接获取,从而提高效率。

动态规划通常适用于满足两个要素的问题:

  1. 重叠子问题: 原问题可以分解成子问题,且这些子问题在解空间中有重叠,即同一个子问题会被多次求解。
  2. 最优子结构: 原问题的最优解可以通过子问题的最优解来构造。

动态规划的解题步骤:

以计算斐波那契数列为例进行说明。斐波那契数列的定义是:F(0) = 0,F(1) = 1,对于每个 n ≥ 2,F(n) = F(n-1) + F(n-2)。

  1. 定义状态: 确定问题的状态,即原问题和子问题中变化的变量。例如,在计算斐波那契数列问题中,定义状态 dpi 表示第 i 个斐波那契数。
  2. 找到状态转移方程: 确定问题状态之间的转移关系,即从一个状态到另一个状态的过程。例如,在计算斐波那契数列问题中,dpi = dpi-1 + dpi-2,即第 i 个斐波那契数等于前两个斐波那契数的和。
  3. 初始化: 初始化状态的初始值,通常是边界情况,用于保证状态转移的正确性。例如,在计算斐波那契数列问题中,初始化 dp0 = 0dp1 = 1,因为斐波那契数列的前两项是已知的。
  4. 计算顺序: 按照一定的计算顺序,通常是从小规模的子问题逐步求解到原问题。例如,在计算斐波那契数列问题中,这从 i = 2 开始,依次计算 dp2dp3,直到 dpn
  5. 求解原问题: 根据状态转移方程和已计算的子问题解,求解原问题的最优解。例如,在计算斐波那契数列问题中,返回 dpn 即为所求的第 n 个斐波那契数。

代码实现如下

代码语言:java
AI代码解释
复制
public int fib(int n) {
    if (n <= 1) {
        return n;
    }

    int[] dp = new int[n + 1];
    dp[0] = 0;
    dp[1] = 1;

    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }

    return dp[n];
}

在这个例子中,dpi 表示第 i 个斐波那契数,通过循环计算并填充 dp 数组,最终返回 dpn 即为第 n 个斐波那契数。。

动态规划问题分类

常见类型的动态规划问题可以分为一下几类:

  1. 线性动态规划: 问题可以表示为一维数组的状态,例如斐波那契数列。
  2. 区间动态规划: 问题涉及对区间进行划分和计算,例如最长回文子序列。
  3. 背包问题: 问题涉及在限定容量下选择不同的物品以最大化或最小化某个值,例如0/1背包问题。
  4. 最短路径问题: 问题涉及寻找两点之间的最短路径,例如Floyd-Warshall算法。
  5. 树形动态规划: 问题涉及树结构的遍历和计算,例如在树上求解最长路径。

线性动态规划

一个经典的线性动态规划问题是最长递增子序列。问题是找到给定数组中的最长递增子序列的长度。

解题步骤
  1. 定义状态:定义状态 dpi 表示以第 i 个元素为结尾的最长递增子序列的长度。
  2. 找到状态转移方程:dpi 的值可以通过比较第 i 个元素与前面所有元素的关系得到,如果 numsi 大于某个元素 numsj,则 dpi = max(dpi, dpj + 1)。
  3. 初始化:将所有 dpi 初始化为1,因为每个元素本身都是一个递增子序列。
  4. 计算顺序:从左到右遍历数组,对于每个元素,再从数组的开头到该元素,更新 dpi 的值。
  5. 求解原问题:最终,原问题的解就是 dp 数组中的最大值。
代码实现
代码语言:java
AI代码解释
复制
public static int lengthOfLIS(int[] nums) {
    if (nums == null || nums.length == 0) {
        return 0;
    }

    int n = nums.length;
    int[] dp = new int[n];
    Arrays.fill(dp, 1);

    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }

    int maxLength = 1;
    for (int length : dp) {
        maxLength = Math.max(maxLength, length);
    }

    return maxLength;
}

在这个例子中,dpi 表示以第 i 个元素为结尾的最长递增子序列的长度,通过循环计算并填充 dp 数组,最终返回 dpn 即为最长递增子序列的长度。

区间动态规划

一个经典的区间动态规划问题是最长回文子序列

问题:找到给定字符串中的最长回文子序列的长度。

解题步骤
  1. 定义状态:定义状态 dpi 表示字符串从第 i 个字符到第 j 个字符之间的最长回文子序列的长度。
  2. 找到状态转移方程:如果 si == sj,则 dpi = dpi+1 + 2,因为两端的字符相等,可以加上这两个字符构成更长的回文子序列。如果 si != sj,则 dpi = max(dpi+1, dpi),取两种情况中的最大值。
  3. 初始化:对角线上的元素初始化为1,即 dpi = 1,表示单个字符是回文子序列。
  4. 计算顺序:从下到上、从左到右的顺序填充 dp 数组。
  5. 求解原问题:返回 dp0,其中 n 是字符串的长度,即为整个字符串的最长回文子序列的长度。
代码实现
代码语言:java
AI代码解释
复制
public static int longestPalindromeSubseq(String s) {
    int n = s.length();
    int[][] dp = new int[n][n];

    // 初始化:单个字符是回文子序列
    for (int i = 0; i < n; i++) {
        dp[i][i] = 1;
    }

    // 计算顺序
    for (int len = 2; len <= n; len++) {
        for (int i = 0; i <= n - len; i++) {
            int j = i + len - 1;
            if (s.charAt(i) == s.charAt(j)) {
                dp[i][j] = dp[i + 1][j - 1] + 2;
            } else {
                dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
            }
        }
    }
    // 求解原问题
    return dp[0][n - 1];
}

在这个例子中,dpi 表示字符串从第 i 个字符到第 j 个字符之间的最长回文子序列的长度,通过填充 dp 数组,最终返回 dp0 即为整个字符串的最长回文子序列的长度。

背包问题

一个经典的背包问题是0/1背包问题

问题:给定一组物品,每个物品有自己的重量和价值,同时给定一个固定的背包容量,目标是选择一些物品装入背包中,使得装入的物品的总价值最大,且不能超过背包的容量。

解题步骤
  1. 定义状态:定义状态 dpi 表示在前 i 个物品中选择一些物品,使得它们的总重量不超过 w 时的最大总价值。
  2. 找到状态转移方程:dpi 的值可以通过比较两种情况得到:选择第 i 个物品和不选择第 i 个物品。如果选择第 i 个物品,dpi = dpi-1w - weighti] + valuei,否则 dpi = dpi-1。
  3. 初始化:初始化 dp0 = 0 和 dpi = 0,表示在背包容量为0时或者没有物品可选时,总价值为0。
  4. 计算顺序:从 i = 1 到 n,从 w = 1 到 W,按照状态转移方程计算 dpi。
  5. 求解原问题:返回 dpn,其中 n 是物品的数量,W 是背包的容量,即为问题的最优解。
代码实现
代码语言:java
AI代码解释
复制
public static int knapsack(int[] weights, int[] values, int capacity) {
    int n = weights.length;
    int[][] dp = new int[n + 1][capacity + 1];

    // 计算顺序
    for (int i = 1; i <= n; i++) {
        for (int w = 1; w <= capacity; w++) {
            if (weights[i - 1] <= w) {
                dp[i][w] = Math.max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]]);
            } else {
                dp[i][w] = dp[i - 1][w];
            }
        }
    }
    // 求解原问题
    return dp[n][capacity];
}

在这个例子中,dpi 表示在前 i 个物品中选择一些物品,使得它们的总重量不超过 w 时的最大总价值,通过填充 dp 数组,最终返回 dpn 即为问题的最优解。

最短路径问题

一个经典的最短路径问题是Floyd-Warshall算法,它用于寻找图中所有节点对之间的最短路径。

问题:给定一个有向图,每条边都有一个权重,找到每一对节点之间的最短路径。

解题步骤
  1. 定义状态:定义状态 disti 表示节点 i 到节点 j 的最短路径长度。
  2. 找到状态转移方程:Floyd-Warshall算法采用三重循环,对于每一对节点 i 和 j,检查是否存在一个节点 k 使得通过 k 路径到达 j 比直接到达 j 的路径更短:disti = min(disti, disti + distk)。
  3. 初始化:初始化 disti 为直接相连的边的权重,如果没有直接相连的边,则初始化为一个表示无穷大的值。
  4. 计算顺序:三重循环嵌套,外层循环遍历所有节点 k,中间循环遍历所有节点 i,内层循环遍历所有节点 j,按照状态转移方程更新 disti。
  5. 求解原问题:返回 dist 数组,其中 disti 表示节点 i 到节点 j 的最短路径长度。
代码实现
代码语言:java
AI代码解释
复制
public static int[][] floydWarshall(int[][] graph, int V) {
    int[][] dist = new int[V][V];

    // 初始化
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            dist[i][j] = graph[i][j];
        }
    }

    // 计算顺序
    for (int k = 0; k < V; k++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE &&
                    dist[i][k] + dist[k][j] < dist[i][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                }
            }
        }
    }

    // 求解原问题
    return dist;
} 

在这个例子中,disti 表示节点 i 到节点 j 的最短路径长度,通过三重循环嵌套更新 dist 数组,最终返回 dist 数组即为问题的最优解。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
什么样的问题应该使用动态规划?
你是否有这样的困惑?在掌握了一些基础算法和数据结构之后,碰到一些较为复杂的问题还是无从下手,面试时自然也是胆战心惊。究其原因,可以归因于以下两点:
刺槐儿
2023/11/16
5710
动态规划基础知识点(包含文档)
我也不知道为啥要收fei,我普通上传,但是平台好像不能直接看,大家可以试看,因为该文档就两页,还没完善
用户11039529
2024/03/25
1880
【算法学习】动态规划
动态规划(dynamic programming)是一种基础的算法设计思想。作为一种寻找最优解的通用方法,它是在20世纪50年代由美国数学家Richard Bellman(也就是之前最短路径问题中Bellman ford算法的发明者之一)所发明的。
短短的路走走停停
2019/11/10
7760
【算法学习】动态规划
动态规划之武林秘籍
听到 动态规划 这个响亮的大名你可能已经望而却步,那是因为这个响亮的名字真的真的很具有迷惑性,不像递归、回溯和贪心等等算法一样,其文即其意,而动态规划则不同,很容易望文生义,真可谓害人不浅,今天我就带大家一起扒一扒 动态规划 的裤子。
灵魂画师牧码
2020/11/06
9190
动态规划之武林秘籍
精读《算法 - 动态规划》
很多人觉得动态规划很难,甚至认为面试出动态规划题目是在为难候选人,这可能产生一个错误潜意识:认为动态规划不需要掌握。
黄子毅
2022/03/15
6480
精读《算法 - 动态规划》
关于动态规划,你想知道的都在这里了!
在本文中,我将介绍由Richard Bellman在20世纪50年代提出的动态规划(dynamic programming)概念,这是一种强大的算法设计技术——将问题分解成多个小问题,存储它们的解,通过将其结合在一起,最终得到原始问题的解决方案。
AI科技大本营
2020/12/08
4800
关于动态规划,你想知道的都在这里了!
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
动态规划(Dynamic Programming,简称DP)是一种通过将复杂问题分解为较小的子问题并存储其结果来解决问题的算法思想。它通常用于解决具有重叠子问题和最优子结构性质的问题,在算法竞赛中有意想不到的惊喜。
摆烂小白敲代码
2024/09/23
1810
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【JavaScript 算法】动态规划:最优子结构与重叠子问题
最优子结构指的是一个问题的最优解可以由其子问题的最优解构造而成。换句话说,如果我们可以通过解决子问题来解决原问题,那么这个问题就具有最优子结构性质。
空白诗
2024/07/20
8170
【JavaScript 算法】动态规划:最优子结构与重叠子问题
面试+算法之动态规划(Java):斐波那契、背包问题、走棋盘、分苹果、连续子数组最大和、秤砝码、最长公共子串、切割钢条、最长不下降子序列、最优二分搜索树、矩阵链
Dynamic programming,简称DP,动态规划,基础算法之一,维基百科的解释:
johnny666
2024/09/18
3290
【地铁上的面试题】--基础部分--数据结构与算法--动态规划和贪心算法
动态规划是一种解决多阶段决策问题的算法思想,它通过将问题划分为若干个子问题,并保存子问题的解来求解原问题的方法。动态规划的特点包括以下几个方面:
喵叔
2023/07/09
4960
算法修炼之筑基篇——筑基二层中期(讨论一下如何解决动态方程问题,没时间了,快快快看一下)
动态规划(Dynamic Programming)是一种解决优化问题的算法思想,通常用于解决具有重叠子问题性质和最优子结构性质的问题。动态规划将问题分解成一系列重叠的子问题,并通过保存子问题的解来避免重复计算,从而提高算法的效率。
命运之光
2024/03/20
1700
动态规划(dynamic programming)
动态规划的基本思想 动态规划的基本思想在于发现和定义问题中的子问题,这里子问题可也以叫做状态;以及一个子问题到下一个子问题之间 是如何转化的 也就是状态转移方程 因此我们遇到一个问题的时候 应该想一想这个问题是否能用某种方式表示成一个小问题,并且小问题具有最优子结构 最优子结构:问题的最优解由相关子问题的最优解组合而成,这些子问题可以独立求解 关于最优子结构 我们来看2个示例 1、求无权有向图中q-t的最短路径 如果q-t间的最短路径经过了点w  那么我们可以证明 q-w  w-t也均是最短路径   所以无
magicsoar
2018/02/06
1.5K0
动态规划(dynamic programming)
数据结构与算法入门手册
图片 第一部分:算法概述 算法定义:一系列解决问题的清晰易行的步骤和规则。以编程实现,输入为问题实例,输出为问题解。 算法特征:输入、输出、有穷性、确定性、可行性。算法必须有清晰的输入与输出,步骤必须能在有限时间内结束,为任意输入都可以给出解,并且解得出的结果是正确的。 算法类族:递归算法、迭代算法、确定算法、非确定算法、Exact算法、Heuristic算法等。递归算法通过递归解决子问题,迭代通过循环;确定算法对每组输入都给出同样的输出,非确定算法输出随输入变化。Exact算法可以给出最优解,Heuri
疯狂的KK
2023/05/23
6430
数据结构与算法入门手册
经典算法之动态规划
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。在面试笔试中动态规划也是经常作为考题出现,其中较为简单的DP题目我们应该有百分之百的把握顺利解决才可以。
用户3467126
2019/11/26
5790
『ACM-算法-动态规划』初识DP动态规划算法
一、多阶段决策过程的最优化问题 在现实生活中,有类活 动的过程,由于 它的特殊性,可将过程分成若干个互相阶段。在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。当阶段决策的选取不是任意确定的,它依赖于当前面临的状态,又影响以后的发展,当段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线,这个问题看作是个前后关联具有链状结构的 多阶段过程就称为多阶段决策过程,这就称为多阶段决策问题。 多阶段决策过程,是指这样的一类特殊的活动过程,问题可以按时间顺序分解互联系的阶段,在每-个阶段都要作出决策,全部过程的决策是-个决策序列。
风骨散人Chiam
2020/10/28
7320
Python 算法基础篇:动态规划的基本概念与特点
动态规划是一种常用且高效的算法技术,用于解决一类具有重叠子问题和最优子结构性质的问题。在本篇博客中,我们将重点介绍动态规划的基本概念与特点,探讨其在解决典型问题中的应用,并通过实例代码演示动态规划算法的实现,每行代码都配有详细的注释。
小蓝枣
2023/07/22
5850
算法之动态规划
动态规划(Dynamic Programming,简称DP)算法是一种通过将问题(比较复杂)划分为相互重叠的子问题(简单易于求解),并解决子问题来解决原问题的方法。它通常用于优化问题,其中需要找到最优解或最大/最小值。
九转成圣
2024/04/10
2290
算法之动态规划
最喜欢dp动态规划的一次(暑期刷题)
所有的问题可能不止一种方法,但是由于是dp专题,只会讲述dp解题的方法。如果需要别的算法可以看看后续的更新。 同时,这里的dp算法并不一定是最简单的效率最高的解题方法,可能别的算法更适合更方便。
薛定谔方程难
2024/07/25
1800
看一遍就理解:动态规划详解
我们刷leetcode的时候,经常会遇到动态规划类型题目。动态规划问题非常非常经典,也很有技巧性,一般大厂都非常喜欢问。今天跟大家一起来学习动态规划的套路,文章如果有不正确的地方,欢迎大家指出哈,感谢感谢~
捡田螺的小男孩
2021/04/23
6280
看一遍就理解:动态规划详解
算法:动态规划
上面是一个按照时间段发生的任务a,b,c,d,e,f,g,h,有的任务之间会有时间重叠。定义:
用户3578099
2022/04/18
1.8K0
算法:动态规划
推荐阅读
相关推荐
什么样的问题应该使用动态规划?
更多 >
LV.3
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档