数据结构——动态规划

前言

“动态规划”在大一时,就知道了这个词,当时觉得好难好高大上,从此心生畏惧,闻词色变,心理阴影一直留存到现在。

在校招时,也多次被问到动态规划相关的题目。

本篇从一道经典动态规划题目说起,慢慢展开。

从题目讲起

【换钱的方法数】

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

【举例】

arr = [5, 10, 25, 1],aim = 15

则组成 15 元的方法共有 6 种,即 1 张 10 元和 1 张 5元、1 张 10 元 5 张 1 元、3 张 5 元、2张 5 元和 5 张 1 元、1 张 5 元和 10 张 1 元、 15 张 1 元。

【解析】

这道题目足够的经典,经典到校招时面试官面我第一个问题就是这个 = =。

之所以经典,是因为这道题通过暴力递归、记忆搜索和动态规划这 3 种方法均可以解决,同时这 3 种方法也是趋向最优解的过程。

通常情况下,可以通过暴力递归解决的问题都可以通过,暴力递归 -> 记忆搜索 -> 动态规划,这样的优化轨迹来进行优化。

从暴力递归到动态规划

暴力递归方法:

如果 arr[5, 10, 25, 1],aim = 100,分析如下:

  • 取 0 张 5 元,让剩下的 arr[1...N-1](即 10、25、1 元)种货币,组成 100 元,求其方法数,结果记为 r1;
  • 取 1 张 5 元,让剩下的 arr[1...N-1] 种货币,组成 100-5 元,求其方法数,结果记为 r2;
  • 取 2 张 5 元,让剩下的 arr[1...N-1] 种货币,组成 90 元,求其方法数,结果记为 r3;
  • ...
  • 取 20 张 5 元,让剩下的 arr[1...N-1] 中货币,组成 0 元,求其方法数,结果记为 r21;

那么 r0 + r1 + r2 + r3 + ... + r21 即是总的方法数。

在上面的分析中,“让剩下的 arr[..],组成。。。,求其方法数”,这句话其实就表示一个递归的过程。

可定义递归方法 solve(int index, int[] arr, int aim),其中 index 表示用 arr[index...N-1] 种货币,组成 aim 元的方法数。源码如下:

 1     private int process(int[] arr, int aim) {
 2         if (arr == null || arr.length == 0 || aim < 0)
 3             return 0;
 4         return solve(0, arr, aim);
 5     }
 6
 7     private int solve(int index, int[] arr, int aim) {
 8         int res = 0;
 9         if (index == arr.length) {
10             res = aim == 0 ? 1 : 0;
11         } else {
12             for (int i = 0; arr[index] * i <= aim; i++) {
13                 res += solve(index + 1, arr, aim - arr[index] * i);
14             }
15         }
16         return res;
17     }

暴力递归方法

在暴力递归的过程中,其中有很多都是不必要的重复计算,比如当计算过 0 张 5 元加 1 张 10 元后,需要进行 arr[25, 1],aim = 90 的递归,同样的,当计算过 2 张 5 元加 0 张 10 元后,需要进行的也是 arr[25, 1],aim = 90 的递归,显然这两次的递归方法入参是相同的,是完全不必要的计算。

如何减少不必要的计算?因为重复调用的递归方法入参是相同的(因为 arr 为全局变量,所以可以忽略,即只考虑 index 和 aim),所以将第一次调用的返回值保存起来,当下次调用递归方法时,首先判断该入参所对应的结果是否已经保存,如果存在对应的结果,则直接获取,不再进行递归计算。

记忆搜索方法:

为了保存入参对应的结果,这里通过二维数组来保存,即:dp[index][aim]。因为 dp[][] 为整数二维数组,所以其初始值为 0,这里我们就要区分是否进行过递归计算,如果入参对应的值在 dp 中没有保存,则进行递归计算,如果计算的结果为 0,则保存至 dp 中的值为 -1,以此来区分没有进行过递归计算和计算结果为0这两种情况。源码如下:

 1     private int process(int[] arr, int aim) {
 2         if (arr == null || arr.length == 0 || aim < 0)
 3             return 0;
 4         int[][] dp = new int[arr.length + 1][aim + 1];
 5         return solve(0, arr, aim, dp);
 6     }
 7
 8     private int solve(int index, int[] arr, int aim, int[][] dp) {
 9         int res = 0;
10         if (index == arr.length) {
11             res = aim == 0 ? 1 : 0;
12         } else {
13             int dpV;
14             for (int i = 0; arr[index] * i <= aim; i++) {
15                 dpV = dp[index + 1][aim - arr[index] * i];
16                 if (dpV != 0) {
17                     res += dpV == -1 ? 0 : dpV;
18                 } else {
19                     res += solve(index + 1, arr, aim - arr[index] * i, dp);
20                 }
21             }
22         }
23         dp[index][aim] = res == 0 ? -1 : res;
24         return res;
25     }

记忆搜索方法

其实记忆搜索方法可以说是一种特别的动态规划方法,下面看经典的动态规划解决方法。

动态规划方法:

如果 arr 长度为 N,则生成行数为 N,列数为 aim + 1 的矩阵 dp。

时间: 2024-08-07 09:53:46

数据结构——动态规划的相关文章

最大岛屿

最大岛屿时间限制: 1000ms内存限制: 128000KB64位整型: Java 类名: 上一题 提交 运行结果 统计 讨论版 下一题 类型: 没有 没有 难度 lv.1 lv.2 lv.3 lv.4 lv.5 lv.6 lv.7 lv.8 lv.9 lv.10 搜索 数据结构 动态规划 STL练习 高精度计算 图论 几何 数学 矩阵计算 入门题目 字符串 博弈论添加 题目描述 神秘的海洋,惊险的探险之路,打捞海底宝藏,激烈的海战,海盗劫富等等.加勒比海盗,你知道吧?杰克船长驾驶着自己的的战船

检索关键字 nyoj

检索关键字 时间限制: 1000ms 内存限制: 65536KB 64位整型:      Java 类名: 上一题 提交 运行结果 统计 讨论版 下一题 类型: 没有 没有   难度       lv.1       lv.2       lv.3       lv.4       lv.5       lv.6       lv.7       lv.8       lv.9       lv.10   搜索   数据结构   动态规划   STL练习   高精度计算   图论   几何  

[MOOC笔记]第一章XA 动态规划(数据结构)

Fibonacci数列和动态规划 什么是Fibonacci数列? Fibonacci数列指的是这样一个数列 {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,144, ...} 它的第0项是0,第1项是第一个1.从第二项开始,每一项都等于前两项之和.用C语言可以表示为: //Fib(n) = Fib(n-1) + fib(n-2) int Fib(int n) { return(2 > n) ? n : Fib(n-1) + Fib(n-2); } 这段代码可以计

数据结构与算法学习之路:背包问题的贪心算法和动态规划算法

一.背包问题描述: 有N种物品和一个重量为M的背包,第i种物品的重量是w[i],价值是p[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包重量,且价值总和最大. 二.解决方法: 1.贪心算法:贪心算法基于的思想是每一次选择都作当前最好的选择,这样最后的结果虽然不一定是最优解,但是也不会比最优解差很多. 举个例子说明可能好懂一些:一帮基友去聚餐,菜是一份一份上的,我每一次夹菜都只夹牛肉/海鲜吃,可能到最后我吃的牛肉/海鲜很多,但不一定代表我吃掉的东西的总价值最高,但是相对来说价值也很高

树形DP——动态规划与数据结构的结合,在树上做DP

本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法与数据结构的第15篇,也是动态规划系列的第4篇. 之前的几篇文章当中一直在聊背包问题,不知道大家有没有觉得有些腻味了.虽然经典的文章当中背包一共有九讲,但除了竞赛选手,我们能理解到单调优化就已经非常出色了.像是带有依赖的背包问题,和混合背包问题,有些剑走偏锋,所以这里不多做分享.如果大家感兴趣可以自行百度背包九讲查看,今天我们来看一个有趣的问题,通过这个有趣的问题,我们来了解一下在树形结构当中做动态规划的方法. 这个问题题意很

算法导论 第四部分——基本数据结构——第15章:动态规划

前言:动态规划的概念 动态规划(dynamic programming)是通过组合子问题的解而解决整个问题的.分治算法是指将问题划分为一些独立的子问题,递归的求解各个问题,然后合并子问题的解而得到原问题的解.例如归并排序,快速排序都是采用分治算法思想.本书在第二章介绍归并排序时,详细介绍了分治算法的操作步骤,详细的内容请参考:http://www.cnblogs.com/Anker/archive/2013/01/22/2871042.html.而动态规划与此不同,适用于子问题不是独立的情况,也

数据结构 3动态规划

Make it work---------------- 递归 Make it right---------------- 递归 Make it fast----------------- 迭代 所谓的动态规划:就是用递归初步给出,让后用迭代等价的替换. fib(n) = fib(n - 1) + fib(n -2) +...........+ ........   :  递归 T(n) = T(n - 1) + T(n - 2) + 1,n >1 s(n) = [T(n) + 1] / 2;

【算法数据结构Java实现】Java实现动态规划(背包问题)

1.背景 追随着buptwusuopu大神的脚步,最近在研习动态规划.动态规划应该叫一种解决问题的思想,记得又一次去某公司面试就被问到了这个. 多于动态规划的理解,大致是这样的,从空集合开始,每增加一个元素就求它的最优解,直到所有元素加进来,就得到了总的最优解. 比较典型的应用就是背包问题,有一个重量一定的包,有若干件物品,他们各自有不同的重量和价值,怎样背才能取得最大价值. 错误的理解:去价值/重量比最大的物品多装(之前我也是这么想的,但是在背包重量一定的情况下这么做并不合理,范例很容易想到)

野生前端的数据结构练习(11)动态规划算法

一.动态规划算法 dynamic programming被认为是一种与递归相反的技术,递归是从顶部开始分解,通过解决掉所有分解出的问题来解决整个问题,而动态规划是从问题底部开始,解决了小问题后合并为整体的解决方案,从而解决掉整个问题. 动态规划在实现上基本遵循如下思路,根据边界条件得到规模较小时的解,小规模问题合并时依据递推关系式进行,也就是说较大规模的问题解可以由较小问题的解合并计算得到.最经典易懂的就是使用递归算法和动态规划算法两种不同的方式来计算斐波那契数列或求阶乘的对比,动态规划算法的特