PREFIX SUM › PREFIX SUM
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →A closing hour j creates two regions: hours 0 through j - 1 remain open, while hours j through n - 1 are closed. An N in the first region costs one because the shop stayed open without a customer. A Y in the second region costs one because the shop was closed when a customer arrived. The penalty is exactly the sum of those two counts.
This turns the problem into evaluating every boundary from 0 to n. For each boundary, you need the number of N characters on its left and the number of Y characters on its right. A prefix count supplies the first value, and a suffix count supplies the second. Scanning boundaries in reverse keeps the suffix count available without building a second array; accepting equal penalties while scanning right to left preserves the earliest hour.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | The prefix construction visits each character once, and the boundary scan evaluates n + 1 positions with constant work at each one. The two passes are sequential rather than nested, so their costs add to O(n). |
| Space | O(n) | prefixN stores one count for every boundary from 0 to n. The scalar suffix count and answer variables use O(1) additional space. The returned integer is required output and is excluded; the working bound stays O(n) for every input shape. |
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
int bestClosingTime(string customers) {
int n = static_cast<int>(customers.size());
vector<int> prefixN(n + 1, 0);
for (int i = 0; i < n; ++i) {
prefixN[i + 1] = prefixN[i] + (customers[i] == 'N' ? 1 : 0);
}
int suffixY = 0;
int minPenalty = n;
int bestHour = n;
for (int j = n; j >= 0; --j) {
int penalty = prefixN[j] + suffixY;
if (penalty <= minPenalty) {
minPenalty = penalty;
bestHour = j;
}
if (j > 0 && customers[j - 1] == 'Y') {
++suffixY;
}
}
return bestHour;
}
};The order inside the reverse loop is the key detail. At the start of iteration j, suffixY represents indices j through n - 1, so the penalty is correct only before adding customers[j - 1]. The update then prepares the count for boundary j - 1. The less-than-or-equal comparison is equally deliberate: reverse scanning sees larger hours first, so a tie must replace the stored answer with the smaller current hour.
You can avoid prefixN by starting with the penalty for closing at hour 0. At that boundary every Y is in the closed region, so the initial penalty is the total number of Y characters. Moving the boundary from j to j + 1 changes only customers[j]: an N becomes correctly open and adds one penalty, while a Y stops being incorrectly closed and removes one penalty.
#include <string>
using namespace std;
class Solution {
public:
int bestClosingTime(string customers) {
int penalty = 0;
for (char customer : customers) {
if (customer == 'Y') {
++penalty;
}
}
int bestPenalty = penalty;
int bestHour = 0;
for (int j = 0; j < static_cast<int>(customers.size()); ++j) {
if (customers[j] == 'Y') {
--penalty;
} else {
++penalty;
}
if (penalty < bestPenalty) {
bestPenalty = penalty;
bestHour = j + 1;
}
}
return bestHour;
}
};This is a genuine optimisation, not merely a different arrangement. It still takes O(n) time, but its extra space falls from O(n) to O(1) because it stores only the current penalty and best answer. The strict comparison is correct here because the scan moves from smaller hours to larger hours: keeping the first occurrence of a minimum automatically keeps the earliest closing hour.