美文网首页
面向leetcode学习动态规划

面向leetcode学习动态规划

作者: bysir | 来源:发表于2019-01-26 21:22 被阅读0次

    什么是动态规划

    20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。

    道理都懂, 但做题总感觉差点什么

    接下来我会取3个leetcode的动态规划题来举例, 读者最好先试着做做哦.

    它们分别是

    (go代码算法可参看我的github)

    不知道思路的话, 试一试以下三个步骤.

    先弄清楚解的依赖关系

    比如221题, 某个点的解由上,左,左上3个点决定, 764题, 点的解由上,下,左,右4个点决定.

    如何找依赖关系呢? 我的方法是将例子写在本子上, 人脑去验算, 尝试找到每个解之间的关系, 当然多做两个题之后也就孰能生巧了.

    思考过程也是有套路的:

    1. 弄清楚在每一步求解过程中有那些变量控制解
    • 如211题, 对于每一个i行j列都有一个解去对应, 变量就是i行j列, 764题同样.
    • 再说322题, 题目中硬币是无限的, 在求每一步的解时, 硬币没有发生任何变化, 那么影响解的变量只有amount.
    1. 变量依次改变(一般是从小变大), 去演算解, 如在求斐波那契数列第10项时, 先去演算第一项, 再去演算第二项, 你就会发现两项之间的关系.
    • 如211题, p[i,j]p[i-1,j-1]之间的关系就是: 至少p[i-1,j-1]是正方形, p[i,j]才能是正方形,
      再思考p[i,j]p[i,j-1]的关系也是同理, 现在你就得到了解之间的关系.
    • 如322题, p[amount]p[amount-1]的关系就是: 如果我取出了面值为1的硬币, 那么所需硬币的最小数量p[amount]=1(取走了1个硬币)+p[amount-1(硬币面额为1)],
      再推理一下, 这里的1其实是硬币的面额, 替换下变量既得到p[amount] = 1 + p[amount - coins[i]]

    最后将思考结果用公式写出来, 方便写代码.

    公式可以看做黑盒的方法, 可不用管题目参数, 直接写变量和解之间的公式.

    • 211题
    p[i,j] = min(1 + p[i-1,j], 1 +  p[i,j-1], 1 +  p[i-1,j-1])
    
    • 764题
    p[i,j] = min(1 + p[i-1,j], 1 +  p[i+1,j], 1 +  p[i,j-1], 1 +  p[i,j+1])
    
    • 322题
    p[amount] = min(1 + p[amount - coins[i]], i=0~len(coins)) 
    

    ps: 如果不能理解, 就在参考看看每个题的注释: 211, 764, 322.

    想好如何保存解

    弄清楚解的依赖关系之后, 还需要弄清楚如何将上一步的解保存下来提供给下一步利用.

    注意这里的保存是一个广泛的意思, 不一定是存在一个全局的数据结构里(如数组), 也可以存在局部变量中.

    特别是做大量数据的题时候, 尽量用局部变量去存储这一步的解, 实在没办法再想到用全局去存.

    举个例子: 求斐波那契数列的第10项

    这个题有两种解法,

    1. 声明一个数组s [10]int, 循环10次, s[i] = s[i-1] + s[i-2]
    2. 声明两个变量 a,b, 循环10次, c=a+b; a=c

    可以看到解法1所需要的空间比2大得多, 当题目没要求需要得到每一步的解的时候, 最好使用解法2.

    当然两种解法的差别还不只是空间.

    如764题, 每个点的解由上,下,左,右4个点决定, 那么如果要将解存到全局的话, 需要声明一个复杂的数据结构以保存4个解, 最后写出来又复杂, 所需要空间也大.

    所以更好的方式是在遍历过程中, 直接用局部变量保存上一步的解, 具体做法看代码啦.

    循环(遍历)

    最后 循环也很关键, 它直接影响了结果的正确性 和 保存解的复杂度.

    对于每一个题, 解的公式不一样, 所以遍历的先后顺序也不一样.

    但其实只需要记得保持一个原则: 按照依赖关系去遍历, 保证上一步解在本次操作之前已经正确的解出来了.

    所以要将解的依赖关系理解透彻.

    在写代码的过程中, 记得反复推敲此行代码所依赖的上一步解是否已经解过了

    (笔者也才开始做leetcode, 没啥心得, 不能落笔于此, 惭愧. 先题海战术吧~)

    具体做法就看我的github吧. 211题, 764题, 322题

    参考

    ps: 学习的第一步一定是仿照(抄袭), 写不出代码就先把别人的代码抄懂, 这很正常. 不过之后记得多总结, 思考别人是怎么想到答案的, 最后尝试用学到的几种不同的思维方式去解题.

    ps: 做题真费脑子, 希望自己能坚持, 等到变强了, 做题也就轻松了.

    相关文章

      网友评论

          本文标题:面向leetcode学习动态规划

          本文链接:https://www.haomeiwen.com/subject/mhhwjqtx.html