算法-动态规划(1)最大子序和问题

从如何理解动态规划?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
LeetCode运行结果
对比算法-分治最大子序和问题, 从打败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, 得到最优解
image.png

你可能感兴趣的:(算法-动态规划(1)最大子序和问题)