LeetCode 1052中等滑动窗口
爱生气的书店老板 图解题解
这道题到底在问什么
给每分钟顾客数 customers 与是否生气 grumpy(1 生气)。老板可让连续 minutes 分钟不生气一次,求最多满意顾客数。
- 输入
- customers=[1,0,1,2,1,1,7,5], grumpy=[0,1,0,1,0,1,0,1], minutes=3
- 输出
- 16
最优解:一步一步想明白
- 3记住「绿色白赚记 base,红色用一个宽 minutes 的窗口挑挽回最多的一段」,下面每帧都在套它。
- 4先给每分钟上色:绿色是老板不生气的分钟(顾客白白满意),红色是生气的分钟(顾客流失)。技巧能连续盖住 3 分钟,把这段窗口内红色分钟的损失抢回来。
- 5第 0 分钟绿色(不生气),1 位顾客白赚,base 累加到 1。
- 6第 1 分钟红色(生气),0 位顾客流失,先不计入 base——等会儿看技巧能不能盖到它。
- 7第 2 分钟绿色(不生气),1 位顾客白赚,base 累加到 2。
- 8第 3 分钟红色(生气),2 位顾客流失,先不计入 base——等会儿看技巧能不能盖到它。
- 9第 4 分钟绿色(不生气),1 位顾客白赚,base 累加到 3。
- 10第 5 分钟红色(生气),1 位顾客流失,先不计入 base——等会儿看技巧能不能盖到它。
- 11第 6 分钟绿色(不生气),7 位顾客白赚,base 累加到 10。
- 12第 7 分钟红色(生气),5 位顾客流失,先不计入 base——等会儿看技巧能不能盖到它。
- 13把冷静技巧盖在第 0~2 分钟(高亮窗口)。这段里原本生气流失的顾客被挽回,共 0 位。
- 140 没超过当前最优 best = 0,窗口继续右滑。
- 15把冷静技巧盖在第 1~3 分钟(高亮窗口)。这段里原本生气流失的顾客被挽回,共 2 位。
- 162 比之前最优更高,刷新 best = 2,记下窗口起点 1。
- 17把冷静技巧盖在第 2~4 分钟(高亮窗口)。这段里原本生气流失的顾客被挽回,共 2 位。
- 182 没超过当前最优 best = 2,窗口继续右滑。
- 19把冷静技巧盖在第 3~5 分钟(高亮窗口)。这段里原本生气流失的顾客被挽回,共 3 位。
- 203 比之前最优更高,刷新 best = 3,记下窗口起点 3。
- 21把冷静技巧盖在第 4~6 分钟(高亮窗口)。这段里原本生气流失的顾客被挽回,共 1 位。
- 221 没超过当前最优 best = 3,窗口继续右滑。
- 23把冷静技巧盖在第 5~7 分钟(高亮窗口)。这段里原本生气流失的顾客被挽回,共 6 位。
- 246 比之前最优更高,刷新 best = 6,记下窗口起点 5。
- 25最优是把冷静技巧盖在第 5~7 分钟,挽回 6 位。加上白赚的 base 10,最多满意顾客 = 16。
⚠️ 容易写错的地方
✗ 错:把技巧窗口往「不生气」段上盖
✓ 对:只在生气分钟里挽回才有增量
不生气的顾客本来就满意,盖上去 0 收益
✗ 错:窗口滑动时忘了减掉滑出的左端
✓ 对:i ≥ minutes 时减 customers[i−minutes]
不减会把窗口越撑越大,extra 失真
✗ 错:base 把生气分钟也算进去
✓ 对:base 只累加 grumpy==0 的分钟
生气分钟的顾客只能靠技巧挽回,不属于稳拿部分
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def maxSatisfied(self, customers: List[int], grumpy: List[int], minutes: int) -> int:
base = sum(c for c, g in zip(customers, grumpy) if g == 0)
extra = best = 0
for i, c in enumerate(customers):
if grumpy[i]:
extra += c
if i >= minutes and grumpy[i - minutes]:
extra -= customers[i - minutes]
best = max(best, extra)
return base + bestC++
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int minutes) {
int base = 0, extra = 0, best = 0;
for (int i = 0; i < (int)customers.size(); ++i) {
if (!grumpy[i]) base += customers[i];
if (grumpy[i]) extra += customers[i];
if (i >= minutes && grumpy[i - minutes]) extra -= customers[i - minutes];
best = max(best, extra);
}
return base + best;
}
};Java
import java.util.*;
class Solution {
public int maxSatisfied(int[] customers, int[] grumpy, int minutes) {
int base = 0, extra = 0, best = 0;
for (int i = 0; i < customers.length; i++) {
if (grumpy[i] == 0) base += customers[i];
if (grumpy[i] == 1) extra += customers[i];
if (i >= minutes && grumpy[i - minutes] == 1) extra -= customers[i - minutes];
best = Math.max(best, extra);
}
return base + best;
}
}复杂度
时间
O(n)
窗口增量滑动,一遍扫完
空间
O(1)
只用 base/extra/best 三个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 爱生气的书店老板 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题滑动窗口和「定长子数组最大和」是一回事吗?+
本质一样:把「生气分钟的顾客数」看成一个数组,求长度为 minutes 的定长窗口最大和,就是 best。再叠加上不生气分钟的固定 base。识别出「定长窗口最大和」这个母题,一类题都能套。
为什么不用前缀和?+
前缀和也能 O(n) 做(窗口和 = 前缀差),但定长窗口用增量滑动更省空间(O(1)),无需额外前缀数组。两种都对,滑窗更轻。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 爱生气的书店老板 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。