GO语言算法实战2 | 青训营

72 阅读1分钟

GO语言算法实战2 | 青训营

作者:LoHhhha 时间:2023.8.19

题目

1388. 3n 块披萨 - 力扣(LeetCode)

给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨:

  • 你挑选 任意 一块披萨。
  • Alice 将会挑选你所选择的披萨逆时针方向的下一块披萨。
  • Bob 将会挑选你所选择的披萨顺时针方向的下一块披萨。
  • 重复上述过程直到没有披萨剩下。

每一块披萨的大小按顺时针方向由循环数组 slices 表示。

请你返回你可以获得的披萨大小总和的最大值。

  • 1 <= slices.length <= 500
  • slices.length % 3 == 0
  • 1 <= slices[i] <= 1000

思路

对于本题,关键词为:动态规划分类讨论

我们可以很轻易的看到以下几个性质:

  • 我们不可能挑选相邻的两块披萨(包括第一块和最后一块)
  • 我们只能且必须选择其中的n/3块披萨

我们该如何找出满足选择n/3块披萨的最佳可能?

我们很轻易想到可以用类似01背包的思想,对于每一个状态,我们维护这个状态的维护位置剩余选择

但我们很快发现一个问题:由于我们需要从不相邻的元素完成迁移,该怎么去做状态迁移?

观察dp[idx][k]的性质:维护到下标idx,选择了k块披萨的最优情况。这导致我们可以跳跃地维护我们的状态,在维护状态dp[idx][k]时,若选择idx下标处的披萨,我们只能去考虑dp[idx-2][k-1]的最优,因为我们不能再选择前一块披萨;而不选择idx下标处的披萨,我们可以选择前一块披萨,所以我们可以直接考虑dp[idx-1][k]作为当前状态的转移(注意:idx-1下标处的披萨在dp[idx-1][k]状态并不是一定选择的)。

所以有如下转移方程:

dp[i][j]=max(dp[i-2][j-1]+part[i],dp[i-1][j])

但我们还有一个问题需要考虑:我们怎么保证头尾不同时被选?

有一个简单易行的方法:将原问题分为两个问题。第一个问题是原数组剔除尾部,第二个问题是原数组剔除头部。这样可以简单易行地让头与尾不同时被选择,取两个中的最大值作为答案。

代码实现

func max(a int, b int) int {
    if a>b{
        return a
    }
    return b
}
​
func getRes(slices []int,start int, end int) int{
    n:=end-start+1;
    m:=len(slices)/3
    dp:=make([][]int,n+1)
    for i:=0;i<=n;i++{
        dp[i]=make([]int,m+1)
    }
    // dp[i][j]=max(dp[i-2][j-1]+slices[i],dp[i-1][j])
    dp[1][1]=slices[start]
    for i:=2;i<=n;i++{
        for j:=0;j<=m;j++{
            if j>=1{
                dp[i][j]=max(dp[i-2][j-1]+slices[start+i-1],dp[i-1][j])
            } else{
                dp[i][j]=dp[i-1][j]
            }
        }
    }
    return dp[n][m]
}
​
func maxSizeSlices(slices []int) int {
    n:=len(slices)
    return max(getRes(slices,0,n-2),getRes(slices,1,n-1))
}