做了一些动态规划的题目, 总结了一下思路和分析过程.在此分享
一.分析方法:
动态规划的关键在于如何把一个问题分解成子问题, 分解完问题就可以得出关系式, 得出关系式就可以编码了. 以下是我分解问题的思路:
1.确定分析纬度:
只有一个参考因素的话只需要一维数组记录数据, 比如最长递增序列, 只有自己一个序列在比较;
比如最长子序列,涉及到两个序列, 因为其下标的变化不是同步的, 所以需要用二维数组记录所有可能性.
2.确定数组代表含义:
数组Dp[n]可以代表最终含义,比如例子中的爬楼梯问题, Dp[n]表示上到n阶一共有多少种方式;
有时候直接定义为最终含义不利于分析问题, Dp[n]可以代表当前值的状态,比如最长上升序列中,Dp[n]表示以n元素为最右边界的上升序列的长度;最大回文串中, Dp[i, j]无法记录长度, 记录的是回文状态.
3.分解成子问题:
从Dp(n)出发分析与Dp(n-1)的联系, Dp(n)如何达到Dp(n-1)的状态,自上而下,比如爬楼梯;
从Dp(n-1)出发分析与Dp(n)的联系,Dp(n-1)如何达到Dp(n)的状态,自下而上,例如最长连续子序列;
也可能是Dp(n)与Dp(n-i)和Dp(i),把Dp(n)分解成Dp(n-i)和Dp(i),例如剪绳子问题
4.确定边界
整个过程是从无到有的过程, 起始点就是边界值. 有些边界值比较特殊, 需要一开始就明确
二.例子整理:
1.爬楼梯问题
描述:一次可以上一阶,一次也可以上两阶,问:上到n阶台阶,有几种方式?
分析:
(1).找关系:
n阶台阶可以由n-1阶上一阶到达;也可以由n-2阶上2阶到达.
(2).确定表达式:
Dp[n] = Dp[n-1] + Dp[n-2]
(3).确定边界和初始值:
Dp[0] = 0; Dp[1] = 1; Dp[2] = 2;
2.硬币找零问题
描述:有1,3,5元面额的硬币, 买n元的商品,求买到商品需要支付最少的硬币数?
分析:
(1).找关系:
n元的商品, 可以由n-1元的硬币组合+1个1元面额买到;可以由n-3元的硬币组合+1个3元面额买到;可以由n-5元的硬币组合+1个5元面额买到. 因为题目要求用最少的硬币数,所以需要比较一下,然后获取最少硬币数的组合
(2).确定表达式:
Dp[n] = Min(Dp[n-1] + 1 ,Dp[n-3] + 1,Dp[n-5] +1)
(3).确定边界和初始值:
Dp[0] = 0; Dp[1] = 1; Dp[2] = 2;Dp[3] = 1;Dp[4] = 2;Dp[5] = 1;
3.最小平方和问题
描述:给一个正整数 n, 找到若干个完全平方数(比如1, 4, 9, … )使得他们的和等于 n。你需要让平方数的个数最少
分析:
(1).找关系:
n, 等于n - 1 + 1,平方数为n-1的平方数+1; 等于n - 4 + 4, 平方数为n-4的平方数+1; …
判断获取最小平方个数
(2).确定表达式:
Dp[n] = Min(Dp[n-1] + 1 ,Dp[n-4] + 1,Dp[n-9] +1, …)
(3).确定边界和初始值:
Dp[0] = 0; Dp[1] = 1; Dp[2] = 2;Dp[3] = 3;Dp[4] = 4;…
4.最长递增序列
描述:设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值
分析:
(1).找关系:
首先思考n与n-1之间的关系, LIS[n]表示长度为n的序列的最大递增序列长度, LIS[n-1]表示长度为n-1的序列最大递增序列长度. 假如L[n]的值大于LIS[n-1]中最大递增序列中的最大值,那LIS[n] = LIS[n-1] + 1.否则可能会产生多条长度一样的序列, n增加的时候, 要判断多条序列的最大值, 不方便维护.
另外一种关系是, 记录以数组每个元素为最大值得递增序列的长度记为Dp[n]. n与n-1的关系即为:遍历0 - n-1假如L[n]的值大于L[j], 并且Dp[j] + 1 大于 Dp[i], 那么Dp[i]的长度 = Dp[j]+ 1,最后选出Dp数组最大的即为m.
(2).确定表达式:
(3).确定边界和初始值:
5.最长公共子序列
描述:给定两个序列X和Y,称序列Z是X和Y的公共子序列如果Z既是X的一个子序列,又是Y的一个子序列,求最大长度
分析:
(1).找关系:
子序列不连续:
二维数组关系. 假设获取到最长序列时, X处在i位置, Y处在j位置.Dp(i, j)即为最大长度.
假如X[i] == Y[j], 那么Dp[i, j] = Dp[i - 1, j -1] + 1; 否则Dp[i, j] = Max( Dp[i - 1, j], Dp[i, j - 1] )
子序列连续:
二维数组关系.以Dp[i, j]代表以X[i], Y[j]作为子序列成员的子序列的长度.
假如 X[i] == Y[j], 那么Dp[i, j] = Dp[i - 1, j -1] + 1; 否则Dp[i, j] = 0
相比Dp(i, j)作为两个长度为i, j的序列的最长子序列长度的含义来说, 这样的定义更方便程序记录长度值. 并且最后只需要比较一下, 就可以得出最大值.
(2).确定表达式:
子序列不连续:
Dp[i, j] =0; i == 0 || j == 0
Dp[i, j] = Dp[i - 1, j -1] + 1; X[i] == Y[j]
Dp[i, j] = Max( Dp[i - 1, j], Dp[i, j - 1] ) X[i] != Y[j]
子序列连续:
Dp[i, j] =0; i == 0 || j == 0
Dp[i, j] = Dp[i - 1, j -1] + 1; X[i] == Y[j]
Dp[i, j] = 0; X[i] != Y[j]
(3).确定边界和初始值:
Dp[0, j] = 0; Dp[i, 0] = 0;
6.减绳子问题
描述:给一根长度为N的绳子,请把绳子剪成M段(m,n都是整数),每段绳子的 长度记为k[0],k[1],k[2]…. 获取k[0],k[1],k[2] 的最大乘积
分析:
(1).找关系:
这里是一维数组的关系,设Dp[n]为长度为n的绳子剪出来的最大乘积. Dp[n]和Dp[n-1]的关系不容易找, 那么就来找n与n-i的关系. 长度为n的乘积等于长度为n-i的乘积乘上长度为i的绳子的乘积. 这个值不一定是最大的,所以最后需要轮询遍历出最大值.
(2).确定表达式:
Dp[n] = Max(Dp[n-1]*Dp[i]);
(3).确定边界和初始值:
Dp[n] = n;
7.编辑距离问题
描述:给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
可以对单词进行的操作有三种:插入一个字符;删除一个字符;替换一个字符
例:
输入: word1 = "horse", word2 = "ros"输出: 3
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
分析:
(1).找关系:
定义Dp[i, j]为最小操作数, i为word1的长度, j为word2的长度.
假如W1[i] == W2[j], 那么就下降到Dp[i - 1, j -1];
否则就需要对W1进行操作:替换即为Dp[i - 1, j -1] + 1; 插入即为Dp[i , j - 1] + 1, 理解为单词1长度为i, 单词2长度为j-1, 操作次数为Dp[i , j - 1], 这时候单词2的长度+1, 那么单词1相应地也应该插入一个字符; 删除即为Dp[i - 1, j] + 1
(2).确定表达式:
Dp[i, j] = Dp[i - 1, j - 1]; W1[i] == W2[j]
Dp[i, j] = Min( Dp[i - 1, j - 1], Dp[i - 1, j], Dp[i, j - 1] ) + 1; W1[i] != W2[j]
(3).确定边界和初始值:
Dp[0, j] = j; Dp[x, 0] = x;
8.背包问题
描述:有N件物品和一个容量为V的背包。第i件物品的容量是c[i],价值是v[i]。将物品装入背包可使这些物品的费用总和不超过背包容量,求解价值总和最大值
分析:
(1).找关系:
变量是物品数量i和容量v, 设Dp(i, j)为在容量j中放入i件物品中的某几件所获得的最大价值.Dp之间的联系取决于第i件物品装与不装, 每件物品都有装与不装两种可能性, 也正是这种机制保证了这种解法的全面性.
假如第i件物品不可以装入(c[i] > j), 那么Dp[i, j]的值就等于Dp[i - 1, j]
假如第i件物品可以装入(c[i] < j),并且装入, 那么Dp[i, j] 等于 v[i] + Dp[i, j - c[i]]
假如第i件物品可以装入(c[i] < j),但不装入, Dp[i, j]的值就等于Dp[i - 1, j], 这里需要获取装入和不装入的较大值
(2).确定表达式:
Dp[i, j] = Dp[i - 1, j]; c[i] > j
Dp[i, j] = Max( Dp[i - 1, j], v[i] + Dp[i, j - c[i]] ); c[i] < j
(3).确定边界和初始值:
9.最长回文串
描述:求字符串str的最大回文串
分析:
(1).找关系:
回文串的子串也是回文串, 依据这个就可以分解成子问题. 需要获取的是长度信息, 这里的Dp无法像最大递增序列一样记录长度, 所以只能记录状态. 设Dp[i, j]表示从位置i开始导位置j的字符串是回文串, 值记为true. 那么Dp[i + 1, j - 1]也是回文串, 假如str[i - 1] == str[j + 1], 那么Dp[i - 1, j + 1]也是回文串. 根据此原则, 以起始位置和回文长队作为下标, 通过两层嵌套循环判断出长度为3 的回文串, 长度为3的, 长度为4的…, 最后比较获取最大的长度的
(2).确定表达式:
(3).确定边界和初始值:
Dp[i, i] = true;
初始化Dp[i, i+1]的值