HOT 100 最难的题居然是游戏厂的最爱

写在前面

翻看 网易 历年笔面题单的时候,发现一道有意思的题目。

该题评论区,网易 的踪影很少,反而被那些在 4399 笔试中遇到的同学所攻陷:

*好嘛,所以这道题还是「游戏厂」的最爱?!*🤣

进一步细看,大家对这道题的评价,可谓"惨不忍闻":

但,如果真的是这么难的题。

那不可能没有那两位重量级选手呀,于是乎果然:

这里特别说明一下,上面那位用回溯做出来的同学留言。

回溯属于「爆搜」方案,时间复杂度是指数级别的,必然会 TLE(超时),因此回溯做出来的解法不算通过哈。

我们一起来看看正解是什么。

题目描述

平台:LeetCode

题号:312

n 个气球,编号为 0n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1i + 1 代表和 i 相邻的两个气球的序号。

如果 i - 1i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

求所能获得硬币的最大数量。

示例 1:

css 复制代码
输入:nums = [3,1,5,8]

输出:167

解释:
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

示例 2:

ini 复制代码
输入:nums = [1,5]

输出:10

提示:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> n = n u m s . l e n g t h n = nums.length </math>n=nums.length
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = n < = 300 1 <= n <= 300 </math>1<=n<=300
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 < = n u m s [ i ] < = 100 0 <= nums[i] <= 100 </math>0<=nums[i]<=100

区间 DP

定义 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ l ] [ r ] f[l][r] </math>f[l][r] 为考虑将 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( l , r ) (l, r) </math>(l,r) 范围内(不包含 lr 边界)的气球消耗掉,所能取得的最大价值。

根据题意,我们可以对 nums 进行扩充,将其从长度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 的 nums 变为长度 <math xmlns="http://www.w3.org/1998/Math/MathML"> n + 2 n + 2 </math>n+2 的 arr,其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> a r r [ 1... n ] arr[1...n] </math>arr[1...n] 对应了原数组 nums,而 <math xmlns="http://www.w3.org/1998/Math/MathML"> a r r [ 0 ] = a r r [ n + 1 ] = 1 arr[0] = arr[n + 1] = 1 </math>arr[0]=arr[n+1]=1。

此时易知 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ 0 ] [ n + 1 ] f[0][n + 1] </math>f[0][n+1] 即是答案,不失一般性考虑 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ l ] [ r ] f[l][r] </math>f[l][r] 该如何转移,假设在 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( l , r ) (l, r) </math>(l,r) 范围内最后剩下的气球的编号为 <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k,此时的 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ l ] [ r ] f[l][r] </math>f[l][r] 由「以 <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k 为分割点的两端所产生的价值」和「消耗 <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k 本身带来的价值」两部分组成:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> f [ l ] [ r ] = max ⁡ ( f [ l ] [ k ] + f [ k ] [ r ] + a r r [ l ] × a r r [ k ] × a r r [ r ] ) , k ∈ ( l , r ) f[l][r] = \max(f[l][k] + f[k][r] + arr[l] \times arr[k] \times arr[r]), k \in (l, r) </math>f[l][r]=max(f[l][k]+f[k][r]+arr[l]×arr[k]×arr[r]),k∈(l,r)

为了确保转移能够顺利进行,我们需要确保在计算 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ l ] [ r ] f[l][r] </math>f[l][r] 的时候,区间长度比其小的 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ l ] [ k ] f[l][k] </math>f[l][k] 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ k ] [ r ] f[k][r] </math>f[k][r] 均被计算。

因此我们可以采用先枚举区间长度 len,然后枚举区间左端点 l(同时直接算得区间右端点 r)的方式来做。

Java 代码:

Java 复制代码
class Solution {
    public int maxCoins(int[] nums) {
        int n = nums.length;
        int[] arr = new int[n + 2];
        arr[0] = arr[n + 1] = 1;
        for (int i = 1; i <= n; i++) arr[i] = nums[i - 1];
        int[][] f = new int[n + 2][n + 2];
        for (int len = 3; len <= n + 2; len++) {
            for (int l = 0; l + len - 1 <= n + 1; l++) {
                int r = l + len - 1;
                for (int k = l + 1; k <= r - 1; k++) {
                    f[l][r] = Math.max(f[l][r], f[l][k] + f[k][r] + arr[l] * arr[k] * arr[r]);
                }
            }
        }
        return f[0][n + 1];
    }
}

C++ 代码:

C++ 复制代码
class Solution {
public:
    int maxCoins(vector<int>& nums) {
        int n = nums.size();
        int arr[n + 2];
        arr[0] = arr[n + 1] = 1;
        for (int i = 1; i <= n; i++) arr[i] = nums[i - 1];
        int f[n + 2][n + 2];
        memset(f, 0, sizeof f);
        for (int len = 3; len <= n + 2; len++) {
            for (int l = 0; l + len - 1 <= n + 1; l++) {
                int r = l + len - 1;
                for (int k = l + 1; k <= r - 1; k++) {
                    f[l][r] = max(f[l][r], f[l][k] + f[k][r] + arr[l] * arr[k] * arr[r]);
                }
            }
        }
        return f[0][n + 1];
    }
};

Python 代码:

Python 复制代码
class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        n = len(nums)
        arr = [1] * (n + 2)
        arr[0] = arr[n + 1] = 1
        for i in range(1, n + 1):
            arr[i] = nums[i - 1]
        f = [[0] * (n + 2) for _ in range(n + 2)]
        for clen in range(3, n + 3):
            for l in range(0, n + 2 - clen + 1):
                r = l + clen - 1
                for k in range(l + 1, r):
                    f[l][r] = max(f[l][r], f[l][k] + f[k][r] + arr[l] * arr[k] * arr[r])
        return f[0][n + 1]

TypeScript 代码:

TypeScript 复制代码
function maxCoins(nums: number[]): number {
    const n = nums.length
    const arr = new Array<number>(n + 2).fill(1)
    for (let i = 1; i <= n; i++) arr[i] = nums[i - 1]
    const f = new Array<Array<number>>(n + 2)
    for (let i = 0; i < n + 2; i++) f[i] = new Array<number>(n + 2).fill(0)
    for (let len = 3; len <= n + 2; len++) {
        for (let l = 0; l + len - 1 <= n + 1; l++) {
            const r = l + len - 1
            for (let k = l + 1; k <= r - 1; k++) {
                f[l][r] = Math.max(f[l][r], f[l][k] + f[k][r] + arr[l] * arr[k] * arr[r])
            }
        }
    }
    return f[0][n + 1]
}
  • 时间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n 3 ) O(n^3) </math>O(n3)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n 2 ) O(n^2) </math>O(n2)

总结

看到这里,你是否和下面这两位同学感受一样?🤣 (头像太可爱,不打码了

其实,这只是一道经典的「区间 DP」入门变形题。

首次遇到可能觉得有点无从下手,但其实区间类的 DP 问题通常从题面就给予了极大的暗示。

但凡是这种 回合决策会对当前"左右端点/区间"产生影响,或依赖于当前"左右端点/区间" 的问题,都可以往「区间 DP」去想。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

相关推荐
大前端爱好者1 小时前
React 19 新特性详解
前端
Amagi.1 小时前
Spring中Bean的作用域
java·后端·spring
随云6321 小时前
WebGL编程指南之着色器语言GLSL ES(入门GLSL ES这篇就够了)
前端·webgl
2402_857589361 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊1 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso2 小时前
Rust 快速入门(一)
开发语言·后端·rust
sco52822 小时前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
我爱学Python!2 小时前
面试问我LLM中的RAG,秒过!!!
人工智能·面试·llm·prompt·ai大模型·rag·大模型应用
OLDERHARD2 小时前
Java - LeetCode面试经典150题 - 矩阵 (四)
java·leetcode·面试
原机小子2 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端