这道题我刷的时候标记的是“耗时100”,意思是前后加起来差不多琢磨了100分钟才算彻底通透。老实说,LeetCode 1052(Grumpy Bookstore Owner 爱生气的书店老板)的难度标的是 Medium,但它不像那种纯考模板的题,而是把一个非常典型的滑动窗口场景,藏在了一个“老板会生气”的故事里。如果你直接去硬模拟每一种可能,很容易绕进 O(n²) 的坑里出不来;可一旦想明白“窗口里到底该统计什么”,代码又短得让人怀疑是不是漏了条件。
这篇文章我就按自己的思考路径来写:先还原题目场景,再讲暴力解为什么可行但不够好,接着推导滑动窗口的核心思路,最后把边界条件、细节坑点、以及这一类题在 LeetCode 热门 100 题体系里的位置都捋一遍。不管你是刚开始刷题的新手,还是想快速过一遍经典滑窗思路的老手,这篇都能给你一些直接能用的东西。
1. 这道题的场景到底在说什么
1.1 故事背景与数据含义
题目给了你一个书店老板,他有一个“情绪开关”——grumpy 数组。如果某分钟 grumpy[i] = 1,说明老板这一分钟在生气;如果 grumpy[i] = 0,说明他这一分钟心情正常。与此同时,customers[i] 表示第 i 分钟进店的顾客数。
顾客满不满意度是这样算的:老板生气的那一分钟,进来的顾客会觉得服务体验差,不满意;老板不生气的那一分钟,顾客满意。这里有个很关键的前提:顾客是每分钟来一批,而且在老板生气的时候,这些顾客虽然不满意,但并不会直接走掉——他们还是会在店里消费,只是“满意值”不算数了。
然后老板掌握了一个“情绪管理技巧”:他可以选择连续 X 分钟(题目里叫 minutes)克制自己不生气。这 X 分钟里,哪怕 grumpy[i] 原本是 1,也会因为这波克制而变成“不生气”,从而让这一分钟的顾客全部变成满意。
好,那把故事翻译成代码逻辑,其实就是:有一个长度为 n 的数组,我们要选一段长度固定为 X 的连续区间,让区间内原本因为老板生气而不满意的那些顾客,统统变成满意。最终目标是让整天的“满意顾客总数”最大化。
1.2 为什么这道题容易让人想复杂
我第一次做的时候,第一反应是:我是不是要考虑窗口放在哪里,窗口内的 customers 要重新计算,窗口外要按原样计算?这个想法没有错,但它会引导你写出暴力枚举所有窗口位置的代码,而暴力枚举就意味着 O(n * X) 的复杂度。
还有一个更容易踩的迷惑点:窗口选中的那一段,老板“不生气”了,但窗口外面老板该生气还是生气。也就是说,窗口内的 grumpy 相当于被“全覆盖成 0”了;窗口外保持原样。这就在提示我们,问题可以被拆成两部分:一部分是“老板本来就不生气”的固定满意值,另一部分是“窗口能把多少原本不满意的顾客捞回来”。
理解到这一层,你才算把这个题从“模拟题”上升到“优化题”。后面所有的高效解法,都是从这两个部分的拆分出发的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法:先想清楚再谈优化
2.1 直接枚举窗口起点
不急着上滑动窗口,先写一版最容易想到的暴力做法,它能帮我们验证对题意的理解是否正确。思路非常简单:枚举所有可能的窗口起点 start,窗口范围是 [start, start + minutes - 1]。然后遍历整个数组,判断第 i 分钟是否计入满意值:
- 如果
grumpy[i] == 0,不管在不在窗口里,这一分钟的顾客都满意; - 如果
grumpy[i] == 1,只有当第 i 分钟落在窗口内,才因为老板克制而变成满意。
写成代码就是这样:
python复制class Solution:
def maxSatisfied(self, customers: List[int], grumpy: List[int], minutes: int) -> int:
n = len(customers)
best = 0
for start in range(n - minutes + 1):
cur = 0
for i in range(n):
