zoukankan      html  css  js  c++  java
  • LeetCode总结 -- 一维动态规划篇

    这篇文章的主题是动态规划。 主要介绍LeetCode中一维动态规划的题目, 列表例如以下: 

    Climbing Stairs

    Decode Ways

    Unique Binary Search Trees

    Maximum Subarray

    Maximum Product Subarray

    Best Time to Buy and Sell Stock

    在介绍上述详细题目之前, 我们先说说动态规划的通常思路。 动态规划是一种算法思路(注意这里不要和递归混淆, 其实递归和迭代仅仅是两种不同的实现方法, 并非算法), 用一句话来总结就是, 动态规划是利用存储历史信息使得未来须要历史信息时不须要又一次计 算。 从而达到减少时间复杂度, 用空间复杂度换取时间复杂度目的的方法。 我个人喜欢把动态规划分为下面几步: 
    1) 确定递推量。 这一步须要确定递推过程中要保留的历史信息数量和详细含义, 同一时候也会定下动态规划的维度; 
    2) 推导递推式。

    依据确定的递推量, 得到怎样利用存储的历史信息在有效时间(通常是常量或者线性时间)内得到当前的信息结果; 
    3) 计算初始条件。 有了递推式之后, 我们仅仅须要计算初始条件, 就能够依据递推式得到我们想要的结果了。 通常初始条件都是比較简单的情况, 一般来说直接赋值就可以; 
    4) (可选)考虑存储历史信息的空间维度。 这一步是基于对算法优化的考虑。 一般来说几维动态规划我们就用几维的存储空间是肯定能够实现的。 可是有时我们对于历史信息的要求不高, 比方这一步仅仅须要用到上一步的历史信息, 而不须要更早的了, 那么我们能够仅仅存储每一步的历史信息。 每步覆盖上一步的信息, 这样便能够少一维的存储空间, 从而优化算法的空间复杂度。 
    动态规划的时间复杂度是O((维度)×(每步获取当前值所用的时间复杂度))。

    基本上依照上面的思路。 动态规划的题目都能够解决。 只是最难的通常是在确定递推量, 一个好的递推量能够使得动态规划的时间复杂度尽量低。 

    接下来我们来看看详细题目, 一维动态规划的题目主要分成两类: 

    (1) 第一种是比較简单的, 直接地依照上面步骤就能够解出来的。 确定递归量, 然后按递归式迭代就能够得到。

    这样的类型的题目是: Climbing StairsDecode WaysUnique Binary Search Trees。 
    Climbing Stairs中递推量非常清晰。 就是爬到i级楼梯有多少种可行爬法。 而对于递推式我们能够看出, 要到达i级楼梯, 必须通过i-1级或者i-2级(以为仅仅能爬一级或者两级), 如此能够得到到达i级楼梯的方式有f(i)=f(i-1)+f(i-2)种, 这样递推式也就出来了。

    而初始条件则是一级楼梯是一种解法, 两级楼梯是两种解法(2或者11)。 有了这些接下来递推到n级楼梯返回就可以, 空间复杂度是O(n)(一维动态规划乘以每一步的常量操作)。

    空间上我们发现每一步。仅仅须要前两步的历史信息。 所以我们不须要存储全部历史信息, 仅仅须要保存前两步, 然后迭取代换就能够了, 所以空间复杂度是O(2)=O(1)。 这里相应于上面的第四步。 
    Decode Ways中递推量也是相似的到达第i个字符可解析方式的数量。 递推式比Climbing Stairs略微复杂一些, 要分情况讨论, 主要对于自己和前面一位组成数字的不同要分别处理一下, 这里就不列出来的,大家能够看看Decode Ways -- LeetCode。 尽管是分情况,只是每种情况也是能够常量时间更新信息的, 初始条件依旧是非常easy的case。 空间上也是仅仅须要保存前两步的信息, 所以时间和空间复杂度跟Climbing Stairs都是一样的。 
    Unique Binary Search Trees思路还是相似的。 递推式是稍有不同, 按左右子树划分然后进行累加, 最后归结为卡特兰数的模型。

    这个问题仍然是一维动态规划。 可是求解单步信息时是一个线性操作, 所以时间复杂度是O(n^2)。 而空间上由于每一步都须要前面每一步的全部信息, 所以也无法优化, 是O(n)。

     

    (2) 接下来我们介绍另外一种类型, 尽管也是一维动态规划, 可是差别在于这类题目须要维护两个递推量。 所以思路上有一点技巧。

    只是还是比較有通法的, 我通常把这样的方法称为”局部最优和全局最优解法“。

    这样的方法中我们通常维护两个量。 一个是到眼下为止最好的结果信息(全局最优), 还有一个必须包括新加进来的元素的最好的结果信息(局部最优), 然后还是推导递推式。 计算初始条件, 跟动态规划的通常思路一样了。

     Maximum SubarrayBest Time to Buy and Sell Stock就是这样的类型的题目。 
    Maximum Subarray中对于递推量我们维护两个。一个是到眼下为止最好的子数组, 而还有一个量则是增加当前元素之后。 包括当前元素的最好的子数组, 终于我们是看全局最优的变量的最优值, 而局部最优却是我们在递推过程中维护全局最优所须要的。 递推式还是有点技巧。 第i+1步表达式例如以下: 
      local[i+1]=Math.max(A[i], local[i]+A[i]),就是局部最优是一定要包括当前元素。所以不然就是上 一步的局部最优local[i]+当前元素A[i](由于local[i]一定包括第i个元素,所以不违反条件)。可是假设local[i]是负的。那么加上他就不如不须要的。所以不然就是直接用A[i]; 
      global[i+1]=Math(local[i+1],global[i])。有了当前一步的局部最优,那么全局最优就是当前的局部最优或者还是原来的全局最优(全部情况都会被涵盖进来。由于最优的解假设不包括当前元素,那么前面会被维护在全局最优里面。假设包括当前元素。那么就是这个局部最优)。 

    初始条件都是0或者第一个元素既能够了, 一遍扫过来。 每次迭代更新两个量(都是常量时间), 所以时间是O(n)。 空间上能够看出仅仅须要上一步的信息, 所以仅仅须要保存上一步的全局最优和局部最优就可以。 复杂度是O(2) = O(1)。

     

    Maximum Product Subarray的题目模型跟Maximum Subarray比較相似。仅仅是把加法改成了乘法。思路还是用这种方法,仅仅是注意这里两个负数相乘可能得到更优的乘法结果,所以我们在维护局部最优时把局部的最小值也存下来,这样遇到负数时就能够得到或许更大的乘积。其它就跟Maximum Subarray是一致的了。

    Best Time to Buy and Sell StockMaximum Subarray是全然一样的。 也是维护两个量。 一个是到眼下为止最好的交易(全局最优), 还有一个是在当前一天卖出的最佳交易(局部最优), 其它步骤也是一样的。 这里就不列出来了。 

    能够看出。 上面五道一维动态规划的题目都是依照我前面列出的四个步骤进行求解的, 其实全部动态规划题目都是依照这个基本思路来的。 掌握了套路之后就是看对详细问题要维护的递推量的选择了。这个个人感觉还是比較靠经验的。 熟能生巧。
  • 相关阅读:
    <modules>
    如何禁用Visual Studio 2013的Browser Link功能
    Visual Studio 2013 Web开发、新增功能:“Browser Link”
    MVC中的AppendTrailingSlash以及LowercaseUrls
    js如何关闭当前页,而不弹出提示框
    border-radius实例1
    border-radius讲解2
    border-radius讲解1
    Android服务Service具体解释(作用,生命周期,AIDL)系列文章-为什么须要服务呢?
    最小生成树-Prim算法和Kruskal算法
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/6958184.html
Copyright © 2011-2022 走看看