从如何理解动态规划?Mark一段理解, 生动形象.
How should I explain dynamic programming to a 4-year-old?
底下有个42K赞同的答案,是这样说的:
*writes down "1+1+1+1+1+1+1+1 =" on a sheet of paper*
"What's that equal to?"
*counting* "Eight!"
*writes down another "1+" on the left*
"What about that?"
*quickly* "Nine!"
"How'd you know it was nine so fast?"
"You just added one more"
"So you didn't need to recount because you remembered there were eight!
Dynamic Programming is just a fancy way to say 'remembering stuff to save time later'"
就不翻译了,相信大家都能看懂。
基本思想
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式 [5] 。
适用原则 :
- 最优解
- 无后效性
- 子问题的重叠性
动态规划使用规则
- 确定状态
- 确定边值
- 确定状态转换方程
简单粗暴理解就是
- 内涵分治的思想, 一个大的问题可以通过分解为多个小问题的集合, 可以从这些小问题的最优解推导出大问题的最优解.
- 分治是由大往小递归求解, 动态规划由小到大递推求解.
- 小问题的最优解结果保存, 用于后面更大的问题最优解推导, 不需要重复计算
- 每个动态规划的问题的确定状态, 确定边值,确定状态转换方程可能有多种,需要具体分析. 也就是每个人考虑的使用规则不一定一样, 不一定只有一种方式.
上一篇分治求解 : 算法-分治最大子序和问题
package com.company;
public class dongtaiguihua {
// 最大子序和
public static void main(String[] args) {
int[] temp = {8,-19,5,-4,20};
System.out.println("最终结果 : " + maxSubArray(temp));
}
//确定状态
//确定边值
//确定状态转移方程
//确定状态就是确定最优解. 最优解dp(n)
//从小到大分析. o-->n
// n = 0, {8} , dp(0) = 8,
// n = 1, {8,-19} , dp(1) = max( dp(0) , 0 ) + (-19) = -11,
// n = 2, {8,-19,5} , dp(2) = max( dp(1) , 0 ) + (5) = 5,
// n = 3, {8,-19,5,-4} , dp(3) = max( dp(2) , 0 ) + (-4) = 1,
// n = 4, {8,-19,5,-4,20} , dp(4) = max( dp(3) , 0 ) + (20) = 21,
//确定边值
//dp(0) = 8,
//确定状态转移方程
// 以dp(4) = max( dp(3) , 0 ) + (20) = 21 来看
// 推导
// => n = 4 的时候
// => dp(n) = max( dp(3) , 0 ) + temp[n] = 最大和
public static int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
//确定边值
dp[0] = nums[0];
//保存一个最大值
int max = dp[0];
for (int i = 1; i
对比算法-分治最大子序和问题, 从打败10%+到打败90%+
复杂度分析 :
时间复杂度 一个for循环 : O(n)
优化
回归到动态规划的原理中, 小问题的最优解结果保存, 用于后面更大的问题最优解推导, 不需要重复计算, 那么如何保存以前的值, 是可以考虑优化的点.
上面用的是一个数组保存了所有结果. 现在可以优化到用一个变量保存最大的
public static int maxSubArray(int[] nums) {
//确定边值, 最开始的最优解
int dp = nums[0];
int max = dp;
for (int i = 1; i
复杂度分析 :
空间复杂度从一个数组优化到一个变量, O(n) 优化到O(1).
// 手动整理一遍逻辑
针对上面的for循环, 从小到大推导 {8,-19,5,-4,20} {8,8,8,8,21}
确定边值的时候保存了 dp = 8, dp表示当前的最大子序和i = 1 nums[1] = -19 考虑情况 : dp + nums[1] >= nums[1] ==> dp = dp + nums[1] 8 + -19 >= -19 ===> true dp = 8 + -19 = -11 dp + nums[1] < nums[1] ==> dp = nums[1] 8 + -19 >= -19 ===> false 比较i-1的时候保存的最大子序和 max = Math.max(max, dp); ==> max = Math.max(8, -11) = 8 结果 : 当i=1,最大子序和应该是8 i = 2 nums[2] = 5 考虑情况 : dp + nums[2] >= nums[2] ==> dp = dp + nums[2] -11 + 5 >= 5 ===> false dp + nums[2] < nums[2] ==> dp = nums[2] -11 + 5 < 5 ===> true dp = 5 比较i-1的时候保存的最大子序和 max = Math.max(max, dp); ==> max = Math.max(8, 5) = 8 结果 : 当i=2, 最大子序和应该是8 i = 3 nums[3] = -4 考虑情况 : dp + nums[3] >= nums[3] ==> dp = dp + nums[3] 5 + -4 >= -4 ===> true dp = 5 + (-4) = 1 dp + nums[3] < nums[3] ==> dp = nums[3] 5 + -4 < -4 ===> false 比较i-1的时候保存的最大子序和 max = Math.max(max, dp); ==> max = Math.max(8, -4) = 8 结果 : 当i=3, 最大子序和应该是8 i = 4 nums[4] = 20 考虑情况 : dp + nums[4] >= nums[4] ==> dp = dp + nums[4] 1 + 20 >= 20 ===> true dp = 1 + 20 = 16 dp + nums[4] < nums[4] ==> dp = nums[4] 1 + 20 < 20 ===> false 比较i-1的时候保存的最大子序和 max = Math.max(max, dp); ==> max = Math.max(8, 21) = 16 结果 : 当i=4, 最大子序和应该是21 .... .... 从以前保存的结果一直递推到n, 得到最优解