「青训营 X 码上掘金」主题 4:攒青豆

97 阅读3分钟

当青训营遇上码上掘金

好了参加了冬令营,赶紧参加点活动保命不摆烂好吧,感觉题四简单一点就先写了,这个还是比较经典的动态规划,其实就是leetcode经典的接雨水的动态规划题,感觉dp就是往两个大方向走一个是维度广,出点什么二维三维的还有一个就是这种加点条件往深了走,虽然很菜啊,但是比较喜欢dp所以先写个dp的思路

动态规划

这个方法使用了前缀最大值+后缀最大值 就是取每一个柱子理想的前后攒豆数再减去柱子高度,就是实际上可以承载的豆子数

所以我们假设下标 i 处能攒的豆子等于下标 i 处的豆子能到达的最大高度减去 heigh[i]。所以,就可以直接遍历,分别从左和从右扫描并分别记录左边和右边的最大高度,然后计算每个下标位置能攒的豆子的数量,时间复杂度为O(n^2)。

但是如果可以知道每个位置两边的最大高度,则可以用遍历一遍的时间得到能接的豆子量,所以自然而然的可以想到用dp来记录。前缀和可以参考LeetCode买卖股票的那题,后缀参考最大元素替换。

可以先记录下标 i 的前缀最值和后缀最值,让再求其中的小者减去柱子高度就是当前柱子能攒的豆子数。

我们来看看代码怎么写,设 n 为数组的长度,start为前缀最大值,after为后缀最大值

go语言的代码 攒青豆 - 码上掘金 (juejin.cn)

func min(a ,b int) int{
    if a < b{
        return a
    }
    return b
}

func max(a ,b int) int{
    if a > b{
        return a
    }
    return b
}


func PreMax(height,start []int, n int ){
    for i := 0; i < n; i++{

        if i == 0{
            start[i] = height[i]
        }else {
            // 当不是第一位时如果之前的最大高度比当前高则保留,反之替换
            start[i] = max(start[i - 1],height[i])
        }

    }
}

func BehMax(height,after []int, n int ){
    for i := n - 1; i >= 0; i--{

        if i == n - 1{
            after[i] = height[i]
        }else {
            // 当不是第一位时如果之前的最大高度比当前高则保留,反之替换
            after[i] = max(after[i + 1], height[i])
        }

    }
}

func trap(height []int) int {
    n := len(height)
    start := make([]int,n)
    after := make([]int,n)
    var res int
    PreMax(height,start,n)
    BehMax(height,after,n)
    for i , _ := range height{
        res += min(start[i],after[i]) - height[i]
    }
    return res
}

时间复杂度O(n) 空间复杂度O(n)

双指针

当然不用dp还可以用双指针嘛方法太多辣就写这两个好了。那么用同向快慢指针也可以就是遇到高的把之前的算一遍,但是还是会有点小问题,所以我们这里可以考虑面对面的两个指针,左指针在最左端,右指针在最右端,那么一开始假设中间没有柱子,就可以接(r - l - 1) * min(l,r) 的豆子记为n,然后我们让矮的一侧指针移动,中间分为两种情况

之间没有比l与r更高的柱子

那么能接的青豆数就是 n 减去之间柱子的高度

之间有比l与r更高的柱子

那么移动之前的豆子数存起来将新的柱子为开始重复之前的步骤

时间复杂度O(n) 空间复杂度O(1)