基础算法之-动态规划

动态规划是一种算法技巧,基本思想是:如果一个问题的解,可以拆分成重复多个步骤的子问题,解决当前的问题后,到达一种状态,后一个子问题的求解是建立在现有状态的基础上,最后在每个子问题的最优解的基础上,得出整体的最优解。

《数据结构与算法-Java》这本书提到,动态规划是将递归算法改写为非递归算法,把非递归的子问题答案记录到一个表内,这种技巧叫动态规划。

基本思想

若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用

特点

  • 最优化原理(最优子结构性质)
    最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。
  • 无后效性
    将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
  • 子问题的重叠性
    动态规划算法的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其他的算法。选择动态规划算法是因为动态规划算法在空间上可以承受,而搜索算法在时间上却无法承受,所以我们舍空间而取时间 [8] 。

分治与动态规划

  • 共同点:二者都要求原问题具有最优子结构性质,都是将原问题分而治之,分解成若干个规模较小(小到很容易解决的程序)的子问题.然后将子问题的解合并,形成原问题的解.

  • 不同点:分治法将分解后的子问题看成相互独立的,通过用递归来做。动态规划将分解后的子问题理解为相互间有联系,有重叠部分,需要记忆,通常用迭代来做。

步骤

  1. 分析优化解的结构
  2. 递归地定义最优解的代价
  3. 自底向上地计算优化解的代价保存之,并获取构造最优解的信息
  4. 根据构造最优解的信息构造优化解

典型问题

1. 斐波那契数列

斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n – 1)+F(n – 2)(n ≥ 3,n ∈ N*)即:0、1、1、2、3、5、8、13、21、34、……

1. 问题分解

  1. 当n>2时,依赖n-1的结果和n-2的结果
  2. n-1的结果依赖n-2和n-3的记过。
    递归过程会重复调用f(n-2) 2 次

状态转移方程

递归:

  • f(n)= f(n-1)+ f(n-2)
    循环:
 for (int i = 2; i <= N; i++) {
      cache[i] = cache[i-1] + cache[i-2];
  }
      

2. 01背包类问题

转自:198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

分解:

  1. 第i家的被打劫,要求第i-1家不被打劫,第i-2家及以前的无关
  2. 到第i家打劫的结果最大化利益,前i-1家的金额与前i-2家的金额 + 第i家的金额比较,取较大的一个

状态转移方程

  • dp[i]= max(dp[i-2]+i,dp[i-1]);

实现:

public int massage3(int[] nums) {
       if (nums == null || nums.length == 0) {
        return 0;
    }
    int length = nums.length;
    if (length == 1) {
        return nums[0];
    }
    int[] dp = new int[length];
    dp[0] = nums[0];
    dp[1] = Math.max(nums[0], nums[1]);
    for (int i = 2; i < length; i++) {
        dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
    }
    return dp[length - 1];
}

简单总结

动态规划的问题基本都是递归可以解决的,用空间换时间,用一个表来记录历史完成的结果状态,减少递归的次数。

参考

//www.cnblogs.com/raichen/p/5772056.html
//blog.csdn.net/ailaojie/article/details/83014821
//www.cnblogs.com/hithongming/p/9229871.html