Medium
题目描述
给你一个用字符串 customers 表示的商店客户访问日志,该字符串由字符 ‘N’ 和 ‘Y’ 组成:
- 如果第
i个字符是 ‘Y’,表示第i小时有客户到来 - 如果第
i个字符是 ‘N’,表示第i小时没有客户到来
如果商店在第 j 小时关门(0 <= j <= n),罚金按以下方式计算:
- 商店营业期间每小时没有客户到来,罚金增加 1
- 商店关门期间每小时有客户到来,罚金增加 1
返回商店必须关门以获得最小罚金的最早小时。
注意:如果商店在第 j 小时关门,意味着商店在第 j 小时是关闭的。
示例 1:
输入: customers = "YYNY"
输出: 2
解释:
- 第 0 小时关门的罚金为 1+1+0+1 = 3
- 第 1 小时关门的罚金为 0+1+0+1 = 2
- 第 2 小时关门的罚金为 0+0+0+1 = 1
- 第 3 小时关门的罚金为 0+0+1+1 = 2
- 第 4 小时关门的罚金为 0+0+1+0 = 1
第 2 或第 4 小时关门都能获得最小罚金。由于 2 更早,最优关门时间是 2。
示例 2:
输入: customers = "NNNNN"
输出: 0
解释: 最好在第 0 小时关门,因为没有客户到来。
示例 3:
输入: customers = "YYYY"
输出: 4
解释: 最好在第 4 小时关门,因为每小时都有客户到来。
约束条件:
- 1 <= customers.length <= 10^5
- customers 仅由字符 ‘Y’ 和 ‘N’ 组成
解题思路
解题思路
这是一道典型的前缀和问题。对于每个可能的关门时间 j,罚金由两部分组成:
- 开门期间没有客户的小时数(前
j小时中 ‘N’ 的个数) - 关门期间有客户来的小时数(后面小时中 ‘Y’ 的个数)
方法一:直接模拟(时间复杂度较高)
遍历所有可能的关门时间,分别计算每种情况下的罚金,找出最小值。
方法二:前缀和优化(推荐)
关键观察:在时刻 j 关门的罚金 = 前 j 小时的 ‘N’ 个数 + 后面小时的 ‘Y’ 个数。
我们可以预先计算:
- 前缀 ‘N’ 的个数:
prefixN[j]表示前j小时 ‘N’ 的个数 - 后缀 ‘Y’ 的个数:
suffixY[j]表示从第j小时开始的 ‘Y’ 个数
方法三:一次遍历优化(最优)
更进一步,我们发现当关门时间从 j 移动到 j+1 时:
- 如果
customers[j]是 ‘N’,罚金减少 1(少了一个开门无客户的惩罚) - 如果
customers[j]是 ‘Y’,罚金增加 1(多了一个关门有客户的惩罚)
因此可以通过一次遍历,动态维护罚金变化,找到最小值。
代码实现
class Solution {
public:
int bestClosingTime(string customers) {
int n = customers.length();
int penalty = 0;
// 计算初始罚金(在第0小时关门)
for (char c : customers) {
if (c == 'Y') penalty++;
}
int minPenalty = penalty;
int bestTime = 0;
// 遍历所有关门时间
for (int j = 0; j < n; j++) {
// 从第j小时关门变为第j+1小时关门
if (customers[j] == 'Y') {
penalty--; // 减少一个关门期间有客户的罚金
} else {
penalty++; // 增加一个开门期间无客户的罚金
}
if (penalty < minPenalty) {
minPenalty = penalty;
bestTime = j + 1;
}
}
return bestTime;
}
};
class Solution:
def bestClosingTime(self, customers: str) -> int:
n = len(customers)
# 计算初始罚金(在第0小时关门)
penalty = customers.count('Y')
min_penalty = penalty
best_time = 0
# 遍历所有关门时间
for j in range(n):
# 从第j小时关门变为第j+1小时关门
if customers[j] == 'Y':
penalty -= 1 # 减少一个关门期间有客户的罚金
else:
penalty += 1 # 增加一个开门期间无客户的罚金
if penalty < min_penalty:
min_penalty = penalty
best_time = j + 1
return best_time
public class Solution {
public int BestClosingTime(string customers) {
int n = customers.Length;
int penalty = 0;
// 计算初始罚金(在第0小时关门)
foreach (char c in customers) {
if (c == 'Y') penalty++;
}
int minPenalty = penalty;
int bestTime = 0;
// 遍历所有关门时间
for (int j = 0; j < n; j++) {
// 从第j小时关门变为第j+1小时关门
if (customers[j] == 'Y') {
penalty--; // 减少一个关门期间有客户的罚金
} else {
penalty++; // 增加一个开门期间无客户的罚金
}
if (penalty < minPenalty) {
minPenalty = penalty;
bestTime = j + 1;
}
}
return bestTime;
}
}
var bestClosingTime = function(customers) {
let n = customers.length;
let minPenalty = Infinity;
let bestHour = 0;
for (let j = 0; j <= n; j++) {
let penalty = 0;
for (let i = 0; i < j; i++) {
if (customers[i] === 'N') {
penalty++;
}
}
for (let i = j; i < n; i++) {
if (customers[i] === 'Y') {
penalty++;
}
}
if (penalty < minPenalty) {
minPenalty = penalty;
bestHour = j;
}
}
return bestHour;
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n),其中 n 是字符串长度,只需要遍历一次字符串 |
| 空间复杂度 | O(1),只使用了常数额外空间 |
相关题目
. Grid Game (Medium)