热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

动态规划问题解决方法及示例

什么是动态规划动态规划是求解决策过程最优化的数学方法。如果一个问题可以分解成若干个子问题,并且子问题之间还有重叠的更小的子问题,就可以考虑用动态规划来

什么是动态规划

动态规划是求解决策过程最优化的数学方法。如果一个问题可以分解成若干个子问题,并且子问题之间还有重叠的更小的子问题,就可以考虑用动态规划来解决这个问题。

应用动态规划之前要分析能否把大问题分解成小问题,分解后的每个小问题也存在最优解。如果将小问题的最优解组合起来能够得到整个问题的最优解,那么就可以使用动态规划解决问题。

可以应用动态规划求解的问题主要由四个特点:
1. 问题是求最优解
2. 整体问题的最优解依赖于各个子问题的最优解
3. 大问题分解成若干小问题,这些小问题之间还有相互重叠的更小的子问题
4. 从上往下分析问题,从下往上求解问题




分析

例:给定数组arr,arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法。

思路及优化方法:

这里写图片描述


暴力搜索方法

arr={5、10、25、1},aim=1000

1、用0张5元的货币,让[10,25,1]组成剩下的1000,最终方法数记为res1
2、用1张5元的货币,让[10,25,1]组成剩下的995,最终方法数记为res2
3、用2张5元的货币,让[10,25,1]组成剩下的990,最终方法数记为res3
……
201、用200张5元的货币,让[10,25,1]组成剩下的0,最终方法数记为res201

最终结果为res1+res2+…+res201。

定义递归函数:int p1(arr, index, aim),返回用arr[index]至arr[N-1]的货币面值组成面值aim的方法数。

public class DynamicProgramming {public int coins1(int[] arr, int aim) {if (arr &#61;&#61; null || arr.length &#61;&#61; 0 || aim <0) {return 0;}return process(arr, 0, aim);}/*** 即上述p1方法*/public int process(int[] arr, int index, int aim) {int res &#61; 0;if (arr.length &#61;&#61; index) {return (aim &#61;&#61; 0 ? 1 : 0);}for (int i &#61; 0; arr[index] * i <&#61; aim; i&#43;&#43;) {// arr[index]选i张时&#xff0c;让剩下的货币组成aim-arr[index]*i面额的方法数&#xff0c;即res_i// 总的方法数即为res_0&#43;res_1&#43;...&#43;res_(aim/arr[index])res &#43;&#61; process(arr, index &#43; 1, aim - arr[index] * i);}return res;}
}

优点&#xff1a;简单方便
缺点&#xff1a;重复计算导致多余的递归&#xff0c;最终导致效率低下

如果已经使用0张5元和1张10元的情况下&#xff0c;后续将求&#xff1a;process(arr, 2, 990);
但是如果已经使用了2张5元和0张十元时&#xff0c;也将要求&#xff1a;process(arr, 2, 990);
就会造成重复计算。


记忆搜索方法

思路&#xff1a;使用HashMap记录计算结果。

public class DynamicProgramming {/*** 二维数组map[i][j]的结果代表process(arr, i, j)的返回结果*/private int[][] map;public int coins1(int[] arr, int aim) {if (arr &#61;&#61; null || arr.length &#61;&#61; 0 || aim <0) {return 0;}map &#61; new int[arr.length &#43; 1][aim &#43; 1];return process(arr, 0, aim);}/*** 即上述p1方法*/public int process(int[] arr, int index, int aim) {int res &#61; 0;if (arr.length &#61;&#61; index) {return (aim &#61;&#61; 0 ? 1 : 0);}for (int i &#61; 0; arr[index] * i <&#61; aim; i&#43;&#43;) {int mapValue &#61; map[index &#43; 1][aim - arr[index] * i];/**mapValue为0表示还没有往当前map中对应位置保存值*/if (mapValue !&#61; 0) {res &#43;&#61; (mapValue &#61;&#61; -1 ? 0 : mapValue);} else {// arr[index]选i张时&#xff0c;让剩下的货币组成aim-arr[index]*i面额的方法数&#xff0c;即res_i// 总的方法数即为res_0&#43;res_1&#43;...&#43;res_(aim/arr[index])res &#43;&#61; process(arr, index &#43; 1, aim - arr[index] * i);}}//计算完毕&#xff0c;将计算结果保存至map&#xff0c;由于res可能为0&#xff0c;这里当map&#61;0时表示map中的值还没有计算&#xff0c;等于-1时表示当前值为0map[index][aim] &#61; (res &#61;&#61; 0 ? -1 : res);return res;}public static void main(String[] args) {int[] arr &#61; {5, 10, 25, 1};int cnt &#61; new DynamicProgramming().coins1(arr, 1000);System.out.println(cnt);}
}

动态规划方法

如果arr长度为N&#xff0c;生成行数为N&#xff0c;列数为aim&#43;1的矩阵dp。dp[i][j]的含义是在使用arr[0…i]货币的情况下&#xff0c;组成钱数j有多少种方法。

举例&#xff1a;arr[0]&#61;5
这里写图片描述

这里写图片描述

求每一个位置都需要枚举&#xff0c;时间复杂度为O(aim)。dp一共有N*(aim&#43;1)个位置&#xff0c;对于每个位置要枚举该位置上一行左侧&#xff08;包括正上&#xff09;的所有位置的值&#xff0c;因此总体的时间复杂度为O(N*aim^2)。

code&#xff1a;

public class DynamicProgramming {private int[][] dp;public int coins(int[] arr, int aim) {dp &#61; new int[arr.length][aim &#43; 1];for (int i &#61; 0; i 0] &#61; 1;}for (int i &#61; 0; i 1; i&#43;&#43;) {if (i % arr[0] &#61;&#61; 0) {dp[0][i] &#61; 1;}}for (int i &#61; 1; i for (int j &#61; 1; j 1; j&#43;&#43;) {calDp(arr, i, j); //计算dp[i][j]}}return dp[arr.length - 1][aim];}private int calDp(int[] arr, int i, int j) {int dp_ij &#61; 0;for (int m &#61; 0; j - m * arr[i] >&#61; 0; m&#43;&#43;) {dp_ij &#43;&#61; dp[i - 1][j - m * arr[i]];}dp[i][j] &#61; dp_ij;return dp_ij;}public static void main(String[] args) {int[] arr &#61; {5, 10, 25, 1};int cnt &#61; new DynamicProgramming().coins(arr, 1000);System.out.println(cnt);}
}

上述算法的时间复杂度为O(N*aim^2)。


记忆搜索方法与动态规划方法的联系


  1. 记忆搜索方法就是某种形态的动态规划方法
  2. 记忆搜索方法不关心到达某一个递归过程的路径&#xff0c;只是单纯的对计算过的递归过程进行记录&#xff0c;避免重复的递归过程。
  3. 动态规划的方法则是规定好每一个递归过程的计算顺序&#xff0c;依次进行计算&#xff0c;后面的计算过程严格依赖前面的计算过程。
  4. 两者都是空间换时间的方法&#xff0c;也都有枚举的过程&#xff0c;区别在于动态规划规定计算顺序&#xff0c;而记忆搜索不用规定

再谈什么是动态规划


  1. 其本质是利用申请的空间来记录每一个暴力搜索的计算结果&#xff0c;下次要用结果的时候直接使用&#xff0c;而不再进行重复的递归过程。
  2. 动态规划规定每一种递归状态的计算顺序&#xff0c;依次进行计算。

对上述动态规划问题的优化

这里写图片描述

因为由上述分析可以dp[i][j]&#61;dp[i-1][j]&#43;dp[i-1][j-1*arr[i]]&#43;…&#xff0c;即在上述图中所示&#xff0c;dp[i][j]的值即为上一行的列下标为j-k*arr[i]且>&#61;0&#xff0c;k&#61;0&#xff0c;1&#xff0c;2…的值之和。

而dp[i][j-1*arr[i]]的值同理即为dp[i-1][j-1*arr[i]] &#43; dp[i-1][j-2*arr[i]] &#43; …

因此dp[i][j] &#61; dp[i-1][j] &#43; dp[i][j-arr[i]]。
经过这样化简后的动态规划方法时间复杂度为O(N*aim)。

优化后的代码&#xff1a;

public class DynamicProgramming {private int[][] dp;public int coins(int[] arr, int aim) {dp &#61; new int[arr.length][aim &#43; 1];for (int i &#61; 0; i 0] &#61; 1;}for (int i &#61; 0; i 1; i&#43;&#43;) {if (i % arr[0] &#61;&#61; 0) {dp[0][i] &#61; 1;}}for (int i &#61; 1; i for (int j &#61; 1; j 1; j&#43;&#43;) {calDp(arr, i, j);}}return dp[arr.length - 1][aim];}private void calDp(int[] arr, int i, int j) {// 在这里进行优化if ((j - arr[i]) >&#61; 0) {dp[i][j] &#61; dp[i - 1][j] &#43; dp[i][j - arr[i]];return;}int dpIj &#61; 0;for (int m &#61; 0; j - m * arr[i] >&#61; 0; m&#43;&#43;) {dpIj &#43;&#61; dp[i - 1][j - m * arr[i]];}dp[i][j] &#61; dpIj;}public static void main(String[] args) {int[] arr &#61; {5, 10, 25, 1};int cnt &#61; new DynamicProgramming().coins(arr, 1000);System.out.println(cnt);}
}

动态规划方法的关键点

1、最优化原理&#xff0c;也就是最优子结构性质。这指的是一个最优化策略具有这样的性质&#xff1a;不论过去状态和决策如何&#xff0c;对前面的决策所形成的状态而言&#xff0c;余下的诸决策必须构成最优策略。简单来说就是一个最优化策略的子策略总是最优的&#xff0c;如果一个问题满足最优化原理&#xff0c;就称其具有最优子结构性质。

2、无后效性。指的是某状态下决策的收益&#xff0c;只与状态和决策相关&#xff0c;与到达该状态的方式无关。

3、子问题的重叠性&#xff0c;动态规划将原来具有指数级时间复杂度的暴力搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余&#xff0c;这时动态规划算法的根本目的。




几个例题


例题1&#xff08;LeetCode&#xff09;

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

方法一&#xff1a;动态规划

这里写图片描述

code&#xff1a;

class Solution {boolean dp[][];public String longestPalindrome(String s) {dp &#61; new boolean[s.length()][s.length()];for (int i &#61; 0; i for (int j &#61; 0; j if (i > j) {continue;}if (i &#61;&#61; j) {dp[i][j] &#61; true;} else if (j &#61;&#61; (i &#43; 1)) {dp[i][j] &#61; (s.charAt(i) &#61;&#61; s.charAt(j));}}}for (int i &#61; s.length() - 1; i >&#61; 0; i--) {for (int j &#61; 0; j if (i >&#61; j || j &#61;&#61; (i &#43; 1)) {continue;}dp[i][j] &#61; (dp[i &#43; 1][j - 1] && s.charAt(i) &#61;&#61; s.charAt(j));}}int maxLength &#61; 0, maxI &#61; 0, maxJ &#61; 0;for (int i &#61; 0; i for (int j &#61; 0; j if (i > j) {continue;}if (dp[i][j] && (j - i &#43; 1) > maxLength) {maxLength &#61; j - i &#43; 1;maxI &#61; i;maxJ &#61; j;}}}return s.substring(maxI, maxJ &#43; 1);}
}

方法二&#xff1a;Expand Around Center

这里写图片描述


方法三&#xff1a;Manacher算法

这里写图片描述
Manacher算法解决


推荐阅读
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了在Java中gt、gtgt、gtgtgt和lt之间的区别。通过解释符号的含义和使用例子,帮助读者理解这些符号在二进制表示和移位操作中的作用。同时,文章还提到了负数的补码表示和移位操作的限制。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 摘要: 在测试数据中,生成中文姓名是一个常见的需求。本文介绍了使用C#编写的随机生成中文姓名的方法,并分享了相关代码。作者欢迎读者提出意见和建议。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
author-avatar
品花人生1
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有