我经常对动态规划如何使用矩阵来解决问题感到困惑。 我大致理解矩阵用于存储先前子问题的结果,以便在处理更大问题时可以使用。
但是,如何确定矩阵的维数?我们如何知道矩阵的每一行/列应该表示什么值?即是否有通用的构建矩阵的过程?
例如,如果我们想使用面值为c1,c2,....cn的硬币更改S金额,矩阵的维度应该是多少?每个列/行应该代表什么?
任何指导都将有所帮助。 谢谢!
---------------------------------------------------------
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---------------------------------------------------------
|{} | | | | | | | | | | | |
---------------------------------------------------------
|{1} | | X | | | | | | | | | |
---------------------------------------------------------
|{1, 2} | | | | | | | | | | | |
---------------------------------------------------------
|{1, 2, 10}| | | | Y | | | | | | | Z |
---------------------------------------------------------
X
代表使用硬币{1}
构造金额1的方法数量,Y
代表使用硬币{1, 2, 10}
表示金额3的方法数量,Z
代表使用硬币{1, 2, 10}
表示金额10的方法数量。{}
的第一行填充了0,因为你不能用没有硬币的情况下找零任何正数金额。现在矩阵看起来像这样:---------------------------------------------------------
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---------------------------------------------------------
|{} | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---------------------------------------------------------
|{1} | 1 | X | | | | | | | | | |
---------------------------------------------------------
|{1, 2} | 1 | | | | | | | | | | |
---------------------------------------------------------
|{1, 2, 10}| 1 | | | Y | | | | | | | Z |
---------------------------------------------------------
y
, z
,则元组 (x的值,y的值,z的值)
是在哈希表中存储子问题结果的良好键。如果这些值是正整数,可以使用矩阵即多维数组代替哈希表。让我们开发找到递归关系和识别所需状态以唯一表示子问题的思路,以便解决您对矩阵维度的困惑。
sort(0, n) = merge(sort(0, n/2), sort(n/2, n))
sort
算法的递归关系中,将区间(0, n)的问题分为两个子问题(0, n/2)和(n/2, 0),组合步骤是合并算法。F(n)
。如果面值是p,q,r。F(n-p)
,F(n-q)
和F(n-r)
的答案,即分别组成金额n-p,n-q和n-r所需的最小硬币数,我们可以取这些值中的最小值加1,得到组成金额n
所需的硬币数。F(n-p)
,F(n-q)
和F(n-r)
,组合步骤是取这些值的最小值并加一。F(n) = min(F(n-p), F(n-q), F(n-r)) + 1
# Base conditions
F(0) = 0
F(n) = infinity if n < 0
这里存在最优子结构和重复问题(如果不明显,可以拿一个样例问题并绘制递归树),因此我们可以使用一些存储来避免重复计算。每个子问题都是递归树中的一个节点。
从递推关系可以看出,函数F只需要一个参数,即一个参数足以表示递归树中的子问题/节点,因此可以使用1D数组或键为单个值的哈希表来存储子问题的结果。
- 给定不同面额的硬币和一个金额,找出组成该金额所需的硬币总组合数。
这个问题更加微妙。暂停一下并尝试识别递推关系。
让我们使用与上述问题相同的术语,即假设金额为n,p、q、r是面额。
与上述问题相同的递推式是否适用?如果 F(n)
表示使用给定面额组成n的所有硬币组合的总数,我们是否可以以某种方式组合 F(n-p)
、F(n-q)
和 F(n-r)
得到 F(n)?直接将它们相加行吗?是否有 F(n) = F(n-p) + F(n-q) + F(n-r)
?
以 n = 3 和两种面额 p、q = 1,2 为例。
使用以上递推关系,我们得到答案为3,对应于拆分[1, 1, 1],[1, 2]和[2, 1],这是不正确的,因为[1, 2]和[2, 1]是相同的面额组合。上述递推公式计算的是排列数而不是组合数。为了避免重复结果,我们需要在硬币之间建立顺序。我们可以通过规定 p 在 q 之前,q 在 r 之前来选择它们的顺序。关注每个面额的组合数量。由于我们在可用面额 [p, q, r] 之间强制进行排序。
让我们从 p 开始解决以下递推式。
F(n, only p allowed) = F(n-p, only p allowed)
## Base condition
F(0) = 1 # There is only one way to select 0 coins which is not selecting any coinss
F(n, p and q allowed) = F(n-q, p and q allowed) + F(n, only p allowed)
F(n, p q and r allowed) = F(n-r, p q and r allowed) + F(n, p and q allowed)
i
是面额中的索引。# F(n, i) = with denominations[i] + without denominations[i]
F(n, i) = F(n - denominations[i], i) + F(n, i-1)
## Base conditions
F(n, i) = 1 if n == 0
F(n, i) = 0 if n < 0 or i < 0
fib[i+2] = fib[i+1] + fib[i]
等同于
def fib(i):
return fib(i-1)+fib(i-2]
通过在递归函数中实现记忆化,您可以使其更加明显。
def fib(i):
if( memo[i] == null )
memo[i] = fib(i-1)+fib(i-2)
return memo[i]
如果你的递归函数有K个参数,你可能需要一个K维矩阵。