动态规划是解决一些特定类型的在多项式时间问题的技术。动态编程解决方案比指数暴力方法更快,而且能够很容易地证实它们的正确性。在咱们研究如何动态思考问题以前,咱们须要知道如下两点:编程
重叠的子问题bash
最优子结构性质优化
一般,能够经过使用动态编程来解决须要最大化或最小化某些数量或计数问题的全部问题,这些问题用于计算在特定条件下的排列或某些几率问题。ui
全部动态编程问题都知足重叠子问题属性,大多数经典动态问题也知足最优子结构属性。有时咱们在给定的问题中观察这些属性,确保它可使用动态规划思路来解决。spa
动态规划问题都与状态及其转移有关。这是必须很是细心完成的最基本的步骤,由于状态转移取决于所作的状态定义的选择。那么,让咱们看看状态究竟是什么意思。code
状态 :一个状态能够定义为一组的能够惟一识别的特定位置,或在给定的问题中的参数。这组参数应尽量小,以减小状态空间。索引
例如:在著名的背包问题中,经过两个参数索引和权重来定义状态,即pack[index] [weight]。这里pack[index] [weight]告诉咱们经过从0到具备袋重量的索引的项目能够得到的最大利润。所以,这里参数索引和权重一块儿能够惟一地识别背包问题的子问题。内存
所以,咱们的第一步是在肯定问题是DP问题后决定问题的状态。咱们知道DP就是使用计算结果来制定最终结果,因此下一步将是找到先前状态之间的关系以达到当前状态。leetcode
这里举个例子,借助leetcode上面的习题来吧,下面是问题的详细内容:class
假设你正在爬楼梯。须要 n 阶你才能到达楼顶。
每次你能够爬 1 或 2 个台阶。你有多少种不一样的方法能够爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法能够爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法能够爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
复制代码
接下来用动态规划的方式思考这个问题。首先,决定给定问题的状态,将采用参数n来决定状态,由于它能够惟一地识别任何子问题,接下来将用T(n)来表示一个阶段的状态变量。
如今,咱们须要计算T(n)。到底该怎么来计算呢?注意看里面的括号,其表明上一个子结果的状态量。
1. 1阶
很明显只有一层阶梯,因此T(1) = 1
复制代码
1. (1 阶) + 1 阶
2. 2 阶
因此T(2) = 2
复制代码
1. (1 阶 + 1 阶) + 1 阶
2. (1 阶) + 2 阶
3. (2 阶) + 1 阶
T(3) = 3
复制代码
1. (1 阶 + 1 阶 + 1 阶) + 1阶
2. (1 阶 + 2 阶) + 1阶
3. (2 阶 + 1 阶) + 1阶
4. (1阶 + 1 阶) + 2阶
5. (2阶)+ 2阶
T(4) = 5
复制代码
如今再仔细去思考其中的问题,有没有看到对应的关系,就拿第四层台阶来讲吧,根据上一次台阶能够知道要么是前一层台阶上1阶,要么是前两层上2阶
T(4) = T(3) + T(2)
因此用参数代表就能够获得下面的状态转移表达式
T(n) = T(n-1) + T(n-2) 需知足n>2
下面用代码实现编写表达式的实现。
func climbStairs(n int) int{
if n == 1 {
return 1
}
if n == 2{
return 2
}
return climbStairs(n-1)+climbStairs(n-2)
}
复制代码
这是动态编程解决方案中最简单的部分。由于以前的状态计算是呈指数增加,因此咱们须要存储状态变量,以便下次须要该状态时,能够直接在内存中使用它。 用表明实现示例以下:
func climbStairs(n int) int {
if n == 1 {
return 1
}
if n == 2 {
return 2
}
memo := make(map[int]int)
memo[1] = 1
memo[2] = 2
for i := 3; i <= n; i++ {
memo[i] = memo[i-1] + memo[i-2]
}
return memo[n]
}
复制代码