从八卦的递归到动态规划

题外话:

好久没写博客了,主要觉得自己写的不怎么样,肚子没墨水了。今天又出来榨干肚子里的最后一滴墨水。

进入正题:

前面写过两篇动态规划的题目,今天又遇到动态规划了,想到了一个更容易理解的方法,我们用凑硬币这道题目来加深理解。网络上这个题目已经被写烂了,大多数文章都是复制粘贴的。本文原创。开始讲解之前读者需要了解递归。本文从递归解法讲起直到推出动态规划方法。

问题描述:

(凑硬币问题)给你 k 种面值的硬币,面值分别为 c1, c2 ... ck ,每种硬币的数量无限,
再给一个总金额 amount ,问你最少需要几枚硬币凑出这个金额,如果不可能凑出,算法返回-1
这里我们k = 3 , c1 = 1、c2 = 3、c3 = 5.

1. 递归解法:

形象的理解递归
比如f是一个八卦的人,有一天a和b谈恋爱了,只告诉了c。 f想知道a和b的关系, 就去问e, e说他自己不知道可以帮忙去问d,d说他也不知道可以帮忙去问c(递归调用过程),c呢就告诉了d,d又告诉了e,e告诉了f(回溯过程,回溯过程可以看作答案一步步传到f耳朵里的过程)。
用递归的思想来思考该问题大致如下
我们知道amount=0和amount<0基本情况(c知道a和b的关系),求amount=11的答案(f想知道a和b的关系),ans(11)=ans(11-5)+1 or ans(11)= ans(11-3)+1 or ans(11)= ans(11-1)+1 (f去问了他认识的人e。如果f认识d或者直接认识c他可以更快的知道答案)。递归的回溯过程程序会自动完成。

def colCoins_rec(coins, amount):
    """
    递归解法
    :param coins: 硬币面值
    :param amount: 总金额
    :return: 凑齐总金额的最少硬币数量
    """
    # 基本情况(c知道的情况)
    if amount == 0:
        return 0
    if amount < 0:
        return -1
    
    res = float("inf")
    # 有三种面值的硬币可以选择(f认识的人,因为f只认识e所以只有一个选择)
    for coin in coins:
        #选择(f选择问谁,交给谁来获取答案)
        subproblem = colCoins_rec(coins, amount-coin)
        # 子问题无解,(f问到了错误地人g,g是个孤儿谁都不认识,走到了死胡同,f要问下一个人)
        if subproblem < 0: continue
        # 选择最优的答案(如果f认识c,那么他很快可以获取答案,就可以选择这条信息链)
        res = min(res, 1+colCoins_rec(coins, amount-coin))
    return res

递归方法存在什么问题呢?很明显的一个是使用了函数调用栈,栈在计算机中是一个相对很有限的资源。还有一个是存在重叠子问题,导致了时间复杂度大大增加。LeetCode上这个方法一般是无法通过的。重叠子问题是什么呢?

                    h                  存在直接连线的表示互相认识
                 /     \               如果h想从c那里获得c知道的答案那么他要问认识的f,g同样
                f       g               f,g要问他们认识的人。
              /  \    /  \            观察发现f,e,g被问了多次!这是导致算法复杂度高的主要原因
            g    e   e   f
          /  \   ........
        e     f
      /  \
    c    d

如何解决上述存在的重叠子问题呢?答案是使用备忘录方法。

2备忘录递归方法

备忘录方法是这些人共用一个列表的,如果谁已经知道答案就填在列表对应的元素里里,每个人在问之前先访问列表,如果得到大难就不用再继续问下去了,避免重复的被问。

mem = [-1]*15
def colCoins_rec_mem(coins, amount, mem):
    """
    在递归的解法的基础上加上备忘录解决重叠子问题。
    :param coins: 硬币面值
    :param amount: 总金额
    :param mem: 备忘录
    :return: 最少硬币数量
    """
    # 基本情况(c知道的答案)
    if amount < 0:
        return -1
    if amount == 0:
        return 0
    # 查看备忘录,看看是否计算过(是否被问过)
    if mem[amount] != -1:
        return mem[amount]
    # 选择(询问的过程)
    res = float("inf")
    for coin in coins:
        subproblem = colCoins_rec_mem(coins, amount-coin, mem)
        if subproblem < 0: continue
        res = min(res, 1+colCoins_rec_mem(coins, amount-coin, mem))
        # 得到答案后写进备忘录
        mem[amount] = res
    return res

我们可以看到基于备忘录的递归算法,会在计算(询问)前去查看备忘录,避免重复计算。而且代码与纯递归方法也非常的相似。只多了查看备忘录的过程和获得答案后写进备忘录的操作。这个备忘录解决了重叠子问题,实际上他还是递归方法。我们可以看到其实备忘录记录的就是子问题和原问题的答案,递归方法是自顶向下的以“询问”的方法求得答案,那么可不可以让c主动的向他认识的人扩散答案直到让f知道答案。这个自底向上的“传播”就是动态规划!(好烦c这种人啊,就像我很烦动态规划题目)话不多说看代码

def colCoins_mem(coins, amount):
    """
    自底向上递推出答案
    :param coins:  硬币面值
    :param amount: 总金额
    :return:
    """
    # 构造一个备忘录
    mem = [0]*(amount+1)  # mem[i]表示凑出金额i需要的最少硬币的数量
    # 基本情况(这句代码可以不要)
    mem[0] = 0
    # 开始推导(c开始传播八卦了)
    for i in range(1, amount+1):
        # 从c开始向他认识的人传播消息
        for coin in coins:
            res = i - coin
            if res >= 0:
                mem[i] = mem[res] + 1
            else:
                continue
    return mem[amount]

总结:

我们看看动态规划的要素就是:1、基本情况(知道答案的那个人)2、明确mem里面存储的是什么我们也可以称作状态。3、选择(每个状态有多少转移方方式)4、明确状态转移的细节(状态转移方程)。该文章一气呵成有什么不懂的留言,有空改一下。动态规划就到这里了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343