[LeetCode](week 14)Burst Balloon

(DP动态规划) Leetcode 312. Burst Balloons

第一次做动态规划的题目

题目

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

Note:

  • You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
  • 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

Example:

Input: [3,1,5,8]
Output: 167 
Explanation: nums = [3,1,5,8] --> [3,5,8] -->   [3,8]   -->  [8]  --> []
             coins =  3*1*5      +  3*5*8    +  1*3*8      + 1*8*1   = 167

题目解析与分析

题意

大概就是:有一排气球,每个标了数字,然后戳一个气球得到的奖励是左气球×该气球×右气球,当然如果左或右没有气球就乘1。问该以什么顺序戳气球得到的奖励最大

最笨的思路

枚举法:将所有的情况列举一遍。当有n个气球的时候,第一步我们有n种选择,第二步我们又有n-1个选择......显然全枚举的算法复杂度为O(N!),效率不敢恭维,因此这里就不实现——因为不可能过。

参阅

进一步想法

我们需要去考虑上面枚举法做了什么重复的计算。我们可以想到,给定一组气球,它所能获得的最大的奖励应该和前面已经被戳的气球无关——被戳过的气球只是在求和的时候累积上了而已。

对于给定k<n, 其可能的组合数有 C_n^k 种,我们可以把k(从1开始)的所有情况都记录在内存上,k+1就可以基于k进行计算,那么我们总共需要进行的计算就是
C_n^1+C_n^2+...+C_n^n
这种算法优于O(N!), 但仍然坏于O(2^N); 我们需要更优的算法

分治的想法

我们考虑用分治去思考这一道题。先是正常地考虑分治,我戳爆某个气球,可不可以把剩下的气球分成两堆呢?这是否可行的前提是两堆会不会互相干扰:答案是肯定的,在戳爆某个气球以后,左堆的最右气球会需要右堆的最左气球来进行计算。

这时候我们需要反向的思维:我们正向地想戳气球的过程,当然会导致两堆相互影响。那如果我们考虑的是在这一堆里最后戳爆的那个气球呢?假如A,B,C,D,E中我最后戳C, 那左堆(A,B)在戳的过程中显然会以C为右边界,而右堆(D,E)以C为左边界——这就实现了分治,两个子问题是相互不干扰的。

具体的算法如下:

func maxCoins(nums []int) int {
    
    record := []int{1}
    record = append(record, nums...)
    record = append(record, 1)
    
    fmt.Println(record)
    
    return DC(0, len(record)-1, record)
}


// DC: Calculate the max coins got from (leftB, rightB), boundary exclueded
func DC(leftB, rightB int, record []int) int{
    // Go through every possibility of last balloon burst within the boundaries
    
    if leftB + 1 == rightB {
        return 0
    }
    
    max := 0
    for i := leftB + 1; i < rightB; i++ {
        temp := record[leftB] * record[i] * record[rightB] + DC(leftB, i, record) + DC(i, rightB, record)
        if temp > max {
            max = temp
        }
    }
    return max
}

很可惜这个算法超时了

动态规划-更进一步的优化

但是这并不是结束。我们很容易可以发现这里面有大量重复的运算。这时我们不难想到借用动态规划的思想来解决这个问题——将重复的结果保存到内存当中

func maxCoins(nums []int) int {
    
    record := []int{1}
    record = append(record, nums...)
    record = append(record, 1)
    
    fmt.Println(record)
    mem := make([][]int, len(record))
    for i := range(mem){
        mem[i] = make([]int, len(record))
    }
    return DC(0, len(record)-1, record, mem)
}

// DC: Calculate the max coins got from (leftB, rightB), boundary exclueded
func DC(leftB, rightB int, record []int, mem [][]int) int{

    // Go through every possibility of last balloon burst within the boundaries
    if leftB + 1 == rightB {
        return 0
    }
    
    if mem[leftB][rightB] != 0 {
        return mem[leftB][rightB]
    }
    
    max := 0
    for i := leftB + 1; i < rightB; i++ {
        temp := record[leftB] * record[i] * record[rightB] + DC(leftB, i, record, mem) + DC(i, rightB, record, mem)
        if temp > max {
            max = temp
            mem[leftB][rightB] = temp
        }
    }
    return max
}

很遗憾,这份代码也只打败了20%多的代码。

纯粹的动态规划

上面的局部动态规划这么低的KO率怎能够让我们满足呢?通过上面的代码编写,我们很显然发现可以将递归的过程变成循环。

简单来说,就是先将left + 1 == right的值先全部赋为0(由于Go语言的机制默认初始化为0就不用额外操作了);而后left + 2 == right的值可以根据left + 1 == right的值计算出来;再然后逐渐递增直到算出(0, length+2)即为结果(由于按照题目要求两端都加了1作为墙壁,所以是len+2)。

代码如下:

func maxCoins(nums []int) int {
    
    record := []int{1}
    record = append(record, nums...)
    record = append(record, 1)
    
    fmt.Println(record)
    mem := make([][]int, len(record))
    for i := range(mem){
        mem[i] = make([]int, len(record))
    }
    // mem 全部初始化为 0
    for stretch := 2; stretch < len(record); stretch++ {
        for left := 0; left + stretch < len(record); left++ {
            max := 0
            right := left + stretch
            for i := left + 1; i < left + stretch; i++ {
                temp := record[left] * record[i] * record[right] + mem[left][i] + mem[i][right]
                if temp > max {
                    max = temp
                    mem[left][right] = temp
                }
            }
        } 
    }
    return mem[0][len(record)-1]
}

Runtime: 8 ms, faster than 57.14% of Go online submissions for Burst Balloons.

这样的结果就差强人意了

还能再优化吗

还能再优化。对代码结构可以做进一步的优化

func maxCoins4(nums []int) int {
    
    record := append([]int{1}, nums...)
    record = append(record, 1)
    
    fmt.Println(record)
    mem := make([][]int, len(record))
    for i := range(mem){
        mem[i] = make([]int, len(record))
    }
    // mem 全部初始化为 0
    lens := len(record)
    for stretch := 2; stretch < lens; stretch++ {
        for left := 0; left + stretch < lens; left++ {
            max := 0
            right := left + stretch
            for i := left + 1; i < right; i++ {
                temp := record[left] * record[i] * record[right] + mem[left][i] + mem[i][right]
                if temp > max {
                    max = temp
                    mem[left][right] = temp
                }
            }
        } 
    }
    return mem[0][lens-1]
}

Runtime: 4 ms, faster than 100.00% of Go online submissions for Burst Balloons.

令人满足。

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

推荐阅读更多精彩内容