zoukankan      html  css  js  c++  java
  • 算法03---动态规划(DP)

    概述

      Dynamic programming,缩写:DP 

           通过空间换取时间的算法思想        

      通过将原问题分解为相对简单的子问题的方式来求解复杂问题。通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。

           用一句话解释动态规划就是 “记住你之前做过的事”,如果更准确些,其实是 “记住你之前得到的答案”。

            对于一个动态规划问题,我们只需要从两个方面考虑,那就是 找出问题之间的联系,以及 记录答案,这里的难点其实是找出问题之间的联系,记录答案只是顺带的事情,利用一些简单的数据结构就可以做到。 

    典型应用场景

      爬楼梯:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 

     常见解题逻辑

    • 问题拆解,找到问题之间的具体联系

    • 状态定义

    • 递推方程推导

    • 实现

      这里面的重点其实是前两个,如果前两个步骤顺利完成,后面的递推方程推导和代码实现会变得非常简单。

      这里还是拿 Quora 上面的例子来讲解,“1+1+1+1+1+1+1+1” 得出答案是 8,那么如何快速计算 “1+ 1+1+1+1+1+1+1+1”;

      问题拆解:我们首先可以对这个大的问题进行拆解,这里我说的大问题是 9 个 1 相加,这个问题可以拆解成 1 + “8 个 1 相加的答案”,8 个 1 相加继续拆,可以拆解成 1 + “7 个 1 相加的答案”,… 1 + “0 个 1 相加的答案”,到这里,第一个步骤 已经完成。

      状态定义 其实是需要思考在解决一个问题的时候我们做了什么事情,然后得出了什么样的答案,对于这个问题,当前问题的答案就是当前的状态,基于上面的问题拆解,你可以发现两个相邻的问题的联系其实是 后一个问题的答案 = 前一个问题的答案 + 1,这里,状态的每次变化就是 +1。

          递推方程: dp[i] = dp[i - 1] + 1,这里的dp[i] 记录的是当前问题的答案,也就是当前的状态,dp[i - 1] 记录的是之前相邻的问题的答案,也就是之前的状态,它们之间通过 +1 来实现状态的变更。

           实现:有了状态表示和递推方程,实现这一步上需要重点考虑的其实是初始化,就是用什么样的数据结构,根据问题的要求需要做那些初始值的设定。

     1 public int dpExample(int n) { 
     3     int[] dp = new int[n + 1];  // 多开一位用来存放 0 个 1 相加的结果 
     5     dp[0] = 0;      
     7     for (int i = 1; i <= n; ++i) {
     8         dp[i] = dp[i - 1] + 1;    // 递推方程 
     9     } 
    11     return dp[n];
    12 }
     

    题目:爬楼梯

    假设你正在爬楼梯。需要 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 个楼梯可以从第 n - 1 个楼梯和第 n - 2 个楼梯到达,因此第 n 个问题可以拆解成第 n - 1 个问题和第 n - 2 个问题,第 n - 1 个问题和第 n - 2 个问题又可以继续往下拆,直到第 0 个问题,也就是第 0 个楼梯 (起点)

    • 状态定义

      “问题拆解” 中已经提到了,第 n 个楼梯会和第 n - 1 和第 n - 2 个楼梯有关联,那么具体的联系是什么呢?你可以这样思考,第 n - 1 个问题里面的答案其实是从起点到达第 n - 1 个楼梯的路径总数,n - 2 同理,从第 n - 1 个楼梯可以到达第 n 个楼梯,从第 n - 2 也可以,并且路径没有重复,因此我们可以把第 i 个状态定义为 “从起点到达第 i 个楼梯的路径总数”,状态之间的联系其实是相加的关系。

    • 递推方程

      “状态定义” 中我们已经定义好了状态,也知道第 i 个状态可以由第 i - 1 个状态和第 i - 2 个状态通过相加得到,因此递推方程就出来了 dp[i] = dp[i - 1] + dp[i - 2]

    • 实现

      你其实可以从递推方程看到,我们需要有一个初始值来方便我们计算,起始位置不需要移动 dp[0] = 0,第 1 层楼梯只能从起始位置到达,因此 dp[1] = 1,第 2 层楼梯可以从起始位置和第 1 层楼梯到达,因此 dp[2] = 2,有了这些初始值,后面就可以通过这几个初始值进行递推得到。

    代码示例

     1 public int climbStairs(int n) {
     3     if (n == 1) {
     4         return 1;
     5     }
     6 
     7     int[] dp = new int[n + 1];  // 多开一位,考虑起始位置
     9     dp[0] = 0; 
    dp[1] = 1;
    dp[2] = 2; 11 for (int i = 3; i <= n; ++i) { 12 dp[i] = dp[i - 1] + dp[i - 2]; 13 } 14 15 return dp[n]; 16 }

      

  • 相关阅读:
    工资到帐的快乐排名第四
    如何成为一个具有批判性思维的人?
    别让千里马被驴踢死
    接口协议抓包与分析
    Qt + ffmpeg+SDl (转)
    各种流媒体服务器的架设(二)
    memcpy(转)
    java之public class和class声明区别详解 (转)
    cin、cin.get()、cin.getline()、getline()、gets()等函数的用法 (转)
    const_iterator 与 const iterator
  • 原文地址:https://www.cnblogs.com/clarino/p/11965089.html
Copyright © 2011-2022 走看看