我刷这道题的时候卡了好一会儿,最后调出来的版本耗时能击败100%的提交、内存击败90%的提交,算是比较满意的成绩。LeetCode 1599《经营摩天轮的最大利润》是一道非常典型的模拟题,表面上是算钱,实际上考的是状态维护和边界处理。很多人在“队列清空后要不要继续转”、“什么时候返回-1”这些地方栽跟头,今天就把这道题从头到尾拆一遍。
1. 题目到底在问什么:把摩天轮生意拆成能算清楚的钱
1.1 核心需求解析
先把这个场景说人话。你有一座摩天轮,有4个座舱,每个座舱最多坐4人,也就是说每一轮转动最多能接待16名游客。每次转动,不管有没有坐满、坐了几个人,都得付固定的运行成本 runningCost。每位游客登舱需要支付 boardingCost 美元。
游客不是一个一个来的,而是按数组 customers 排队,customers[i] 表示第 i 次转动前到达的新游客数量。注意这里的下标不是“某一时刻”,而是“新一批游客到达的时间点”。摩天轮转完一圈,这批人可以选择登舱,坐满的座舱转走,剩下的人继续排队等下一轮。
要求算出:在哪个转动次数上,累计利润达到最大值。如果从头到尾利润都不可能为正,就返回 -1。
这道题的核心关键词是“累计利润峰值”,不是“最终利润”,也不是“每股收益最大”。很多人上来就想着用贪心,觉得每次尽量多上人、成本固定,利润肯定单调递增,这就是典型的想当然了。因为每次转动的成本是固定的,而登舱收入取决于当时排队的人数,如果某轮只有1个人坐,收入可能连成本都覆盖不了,那这一轮就是亏损的。可一旦下一轮来了16个人,一轮就能赚回好几轮的成本,累计利润曲线是有波峰的。
1.2 为什么这道题是模拟而不是贪心或动态规划
第一眼看到“最大利润”,很容易往动态规划或者贪心策略上靠。但仔细分析会发现,这道题没有任何“选择”空间,你没法决定这轮让谁上、不让谁上,规则就是“能上多少上多少,最多16人”。也没有状态转移方程可写,因为所有信息都是线性的:排队人数、已转动次数、累计利润,这三个变量是确定的,不需要做最优决策。
这就是典型的模拟题,而且是一道“会者不难,难者不会”的模拟题。它的陷阱不在算法设计,而在边界处理。
我个人的判断标准是:如果一道题的数据范围不大、过程是线性的、每一步的操作都是规定的,那80%以上是模拟。这道题给你数组、给你两个费用参数,摩天轮每一轮发生了什么、来了几个人、上了几个人、花了多少钱,全是明牌,你只需要按时间顺序把账算清楚。
模拟题的难度往往体现在两个地方:一是“过程”能不能被忠实还原,二是“边界条件”会不会漏。1599就是这两点都踩到了。我见过不少人代码逻辑写得挺顺,最后挂在返回-1的判断上,或者挂在“队列清空后是否继续转动”的思路上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心算法拆解:状态机与利润更新逻辑
2.1 状态变量怎么设计
写模拟题的第一步,永远是先把状态变量列清楚。这道题你需要维护的东西其实很少,但每一个都不能漏:
waiting:当前还在排队等待登舱的游客数量。这个不是从数组里直接读的,而是上一轮没上完的人加上新来的人。profit:到目前为止的累计利润。计算方式是:累计登舱收入减去累计运行成本。maxProfit:历史最大累计利润,也就是题目要找的答案。minRounds(或者rounds):达到最大利润时的转动次数。idx:当前处理到customers数组的哪个位置。
核心逻辑是:每一轮转动,先让新游客加入排队队伍(waiting += customers[idx]),然后尽量让 waiting 里的游客登舱,最多上16人,也就是 onboard = min(waiting, 16),然后 waiting -= onboard。登舱收入增加 onboard * boardingCost,运行成本增加 runningCost,所以本轮净利润就是 onboard * boardingCost - runningCost,累加到 profit 上。然后判断 profit 是否超过了之前记录的 maxProfit,如果超过了就更新最大利润和对应的轮次。
这里有一个细节:每次转动都对应一次成本支出,不管有没有人坐。所以哪怕某轮排队
