LeetCode 1052. 爱生气的书店老板
题目描述


题意分析
customers[i]表示某一分钟的顾客数,grumpy[i]表示老板当时是否生气:不生气时这一分钟的顾客满意,生气时则不满意。老板可以使用一次技巧,在连续minutes分钟内保持冷静,求最多能让多少顾客满意。技巧只覆盖一段连续时间,不能把名额分散到多个位置。原本就满意的顾客,无论窗口选在哪里都仍然满意;窗口只可能额外挽回原本生气分钟的顾客。
解法:滑动窗口维护有效区间
核心思路
[!blue]
将总收益拆成两部分。
base是所有grumpy[i] = 0时的顾客总数,与窗口选择无关;额外收益只来自窗口内那些grumpy[i] = 1的分钟。两部分对应不同顾客,相加不会重复。因此窗口每个位置的有效贡献是:原本生气则为该分钟顾客数,否则为
0。不需要真的建立贡献数组,只在加入和移除窗口元素时检查对应的grumpy即可。技巧时长固定,先算出最左边长度为
minutes的窗口额外收益,作为window和当前最大值best。之后每次向右滑动一格,新位置right进入,旧位置right - minutes离开,只需加入和减去这两个位置的有效贡献。滑动覆盖了所有合法的连续时间段,每次维护的
window都恰好是当前段能新增的满意人数。固定收益不变,选择最大的额外收益就是全局最优,最后返回base + best。
解题步骤
- 扫描全部分钟,把原本不生气时的顾客计入
base。- 计算前
minutes个位置中原本生气部分的顾客数,作为第一个窗口收益,并初始化best。- 从下标
minutes开始右移窗口:新进入位置若生气就加上其顾客数,离开位置若生气就减去其顾客数。- 完成一次加减后更新
best,遍历结束返回base + best。
代码实现
class Solution {
public int maxSatisfied(int[] customers, int[] grumpy, int minutes) {
// 先累加本来就满意的顾客,窗口只计算额外挽回的人数。
int base = 0;
for (int i = 0; i < customers.length; i++) {
if (grumpy[i] == 0) {
base += customers[i];
}
}
int window = 0;
for (int i = 0; i < minutes; i++) {
if (grumpy[i] == 1) {
window += customers[i];
}
}
// 首个完整窗口也必须参与最大值比较。
int best = window;
for (int right = minutes; right < customers.length; right++) {
// 只增加新进入窗口且原本不满意的顾客。
if (grumpy[right] == 1) {
window += customers[right];
}
// 移除离开窗口的位置,保持窗口长度不变。
int left = right - minutes;
if (grumpy[left] == 1) {
window -= customers[left];
}
best = Math.max(best, window);
}
return base + best;
}
}
func maxSatisfied(customers []int, grumpy []int, minutes int) int {
// 先累加本来就满意的顾客,窗口只计算额外挽回的人数。
base := 0
for i, customer := range customers {
if grumpy[i] == 0 {
base += customer
}
}
window := 0
for i := 0; i < minutes; i++ {
if grumpy[i] == 1 {
window += customers[i]
}
}
// 首个完整窗口也必须参与最大值比较。
best := window
for right := minutes; right < len(customers); right++ {
// 只增加新进入窗口且原本不满意的顾客。
if grumpy[right] == 1 {
window += customers[right]
}
// 移除离开窗口的位置,保持窗口长度不变。
left := right - minutes
if grumpy[left] == 1 {
window -= customers[left]
}
if window > best {
best = window
}
}
return base + best
}
复杂度分析
- 时间复杂度:$O(n)$,每个位置参与常数次计算。
- 空间复杂度:$O(1)$,只维护固定收益、当前窗口和最大收益。
关键点总结
[!green]
- 基础满意人数与技巧新增人数分开计算,窗口中原本满意的人不会再计一次。
- 窗口长度固定,右移时只改变两个位置,能在线性时间遍历所有选择。
- 首个完整窗口就可能最优,必须在继续滑动前记录它的贡献。
易错点总结
[!yellow]
- 将窗口内全部顾客再加到
base,会重复统计本来已经满意的人。- 移除旧位置时仍检查新右端的生气标记,加入与移除对应不同分钟,条件必须分别读取。
- 移出的下标写成
right - minutes + 1,减掉的仍在新窗口里,维护值就会错位。- 只从第二个窗口开始记录最大值,会漏掉最左窗口,窗口覆盖全数组时尤其明显。
- 挑选若干顾客最多的生气分钟,会违反技巧必须作用于一个连续区间的限制。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 643. 子数组最大平均数 I | 简单 | 先固定不生气时的基础收益,再用定长窗口求能挽回的最大额外收益。 |
| 1004. 最大连续1的个数 III | 中等 | 原题允许在任意位置翻k个0,本题只能选择一个连续时间段,不能把窗口长度当自由修改预算。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!