Minimum Penalty for a Shop
MedPrompt
You are given the customer visit log of a shop represented by a 0-indexed string customers consisting only of characters 'N' and 'Y':
- if the
ithcharacter is'Y', it means that customers come at theithhour - whereas
'N'indicates that no customers come at theithhour.
If the shop closes at the jth hour (0 <= j <= n), the penalty is calculated as follows:
- For every hour when the shop is open and no customers come, the penalty increases by
1. - For every hour when the shop is closed and customers come, the penalty increases by
1.
Return the earliest hour at which the shop must be closed to incur a minimum penalty.
Note that if a shop closes at the jth hour, it means the shop is closed at the hour j.
Example 1:
Input: customers = "YYNY"
Output: 2
Explanation:
- Closing the shop at the 0th hour incurs in 1+1+0+1 = 3 penalty.
- Closing the shop at the 1st hour incurs in 0+1+0+1 = 2 penalty.
- Closing the shop at the 2nd hour incurs in 0+0+0+1 = 1 penalty.
- Closing the shop at the 3rd hour incurs in 0+0+1+1 = 2 penalty.
- Closing the shop at the 4th hour incurs in 0+0+1+0 = 1 penalty.
Closing the shop at 2nd or 4th hour gives a minimum penalty. Since 2 is earlier, the optimal closing time is 2.Example 2:
Input: customers = "NNNNN"
Output: 0
Explanation: It is best to close the shop at the 0th hour as no customers arrive.Example 3:
Input: customers = "YYYY"
Output: 4
Explanation: It is best to close the shop at the 4th hour as customers arrive at each hour.
Constraints:
1 <= customers.length <= 105customersconsists only of characters'Y'and'N'.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach optimizes the penalty calculation by pre-computing the counts of 'N's and 'Y's. The penalty for closing at hour j is the sum of 'N's before j and 'Y's at or after j. We can use a prefix sum array for the 'N's and a suffix sum array for the 'Y's to find these counts in O(1) time for each j.
Algorithm
- Create a
prefixNarray of sizen+1. Populate it such thatprefixN[i]is the count of 'N's incustomersup to indexi-1. - Create a
suffixYarray of sizen+1. Populate it such thatsuffixY[i]is the count of 'Y's incustomersfrom indexito the end. - Initialize
minPenaltyto a large value andbestHourto 0. - Iterate
jfrom0ton. - For each
j, calculatecurrentPenalty = prefixN[j] + suffixY[j]. - If
currentPenaltyis less thanminPenalty, updateminPenaltytocurrentPenaltyandbestHourtoj. - Return
bestHour.
Walkthrough
The core idea is to avoid re-calculating counts for each closing hour. We create a prefixN array where prefixN[i] stores the total number of 'N's in the substring customers[0...i-1]. We also create a suffixY array where suffixY[i] stores the total number of 'Y's in the substring customers[i...n-1]. Both arrays can be computed in O(n) time. Once we have these arrays, the penalty for closing at hour j can be calculated in O(1) time as penalty(j) = prefixN[j] + suffixY[j]. We then iterate from j = 0 to n, calculate the penalty for each j using the pre-computed arrays, and find the hour with the minimum penalty.
class Solution { public int bestClosingTime(String customers) { int n = customers.length(); int[] prefixN = new int[n + 1]; for (int i = 0; i < n; i++) { prefixN[i + 1] = prefixN[i] + (customers.charAt(i) == 'N' ? 1 : 0); } int[] suffixY = new int[n + 1]; for (int i = n - 1; i >= 0; i--) { suffixY[i] = suffixY[i + 1] + (customers.charAt(i) == 'Y' ? 1 : 0); } int minPenalty = Integer.MAX_VALUE; int bestHour = -1; for (int j = 0; j <= n; j++) { int currentPenalty = prefixN[j] + suffixY[j]; if (currentPenalty < minPenalty) { minPenalty = currentPenalty; bestHour = j; } } return bestHour; }}Complexity
Time
O(n). We perform three separate passes over the data: one for `prefixN`, one for `suffixY`, and one to find the minimum penalty. Each pass takes O(n) time.
Space
O(n). We use two extra arrays of size `n+1` to store the prefix and suffix counts.
Trade-offs
Pros
Much more efficient than the brute-force approach.
Linear time complexity is well within the given constraints.
Cons
Requires extra space proportional to the input size, which might be a concern for very large inputs in a memory-constrained environment.
This approach directly simulates the process described in the problem. It iterates through every possible closing hour j from 0 to n (where n is the number of hours). For each potential closing hour, it calculates the total penalty by scanning the customers string.
Algorithm
- Initialize
minPenaltyto a very large value andbestHourto 0. - Iterate through each possible closing hour
jfrom0ton(wherenis the length ofcustomers). - For each
j, calculate thecurrentPenalty:- Initialize
currentPenaltyto 0. - Iterate from
i = 0toj-1. Ifcustomers.charAt(i)is 'N', incrementcurrentPenalty. - Iterate from
i = jton-1. Ifcustomers.charAt(i)is 'Y', incrementcurrentPenalty.
- Initialize
- If
currentPenaltyis less thanminPenalty, updateminPenaltytocurrentPenaltyandbestHourtoj. - After checking all possible hours, return
bestHour.
Walkthrough
The algorithm considers each hour j from 0 to n as a potential closing time. For a given j, the shop is open from hour 0 to j-1 and closed from hour j to n-1. The penalty is calculated by summing the number of 'N's during open hours and the number of 'Y's during closed hours. This calculation is performed from scratch for every possible closing hour. The algorithm keeps track of the minimum penalty found so far and the earliest hour j that yields this minimum.
class Solution { public int bestClosingTime(String customers) { int n = customers.length(); int minPenalty = Integer.MAX_VALUE; int bestHour = -1; for (int j = 0; j <= n; j++) { int currentPenalty = 0; // Penalty for open hours (0 to j-1) for (int i = 0; i < j; i++) { if (customers.charAt(i) == 'N') { currentPenalty++; } } // Penalty for closed hours (j to n-1) for (int i = j; i < n; i++) { if (customers.charAt(i) == 'Y') { currentPenalty++; } } if (currentPenalty < minPenalty) { minPenalty = currentPenalty; bestHour = j; } } return bestHour; }}Complexity
Time
O(n^2), where n is the length of the `customers` string. The outer loop runs `n+1` times, and the inner loops for calculating penalty take O(n) time in each iteration.
Space
O(1), as we only use a few variables to store the state, regardless of the input size.
Trade-offs
Pros
Simple to understand and implement.
Directly follows the problem definition without complex logic.
Cons
Inefficient for large inputs due to its quadratic time complexity.
Will likely result in a 'Time Limit Exceeded' error on platforms with large test cases.
This is the most optimal approach. It builds upon the idea that the penalty for closing at hour j+1 can be derived from the penalty for closing at hour j with a simple adjustment. This allows us to calculate the penalties for all closing hours in a single pass through the customers string, without needing extra arrays.
Algorithm
- Calculate the initial penalty for closing at hour 0. This is equal to the total count of 'Y's in the
customersstring. - Initialize
minPenaltywith this initial penalty andbestHourto 0. LetcurrentPenaltyalso be this value. - Iterate
jfrom1ton. In each iteration,jrepresents the new closing hour. - Update
currentPenaltybased on the character atcustomers[j-1]:- If
customers[j-1] == 'Y', decrementcurrentPenalty. - If
customers[j-1] == 'N', incrementcurrentPenalty.
- If
- If the new
currentPenaltyis strictly less thanminPenalty, updateminPenalty = currentPenaltyandbestHour = j. - Return
bestHour.
Walkthrough
We can analyze the change in penalty when moving the closing time from hour j to j+1. The only hour that changes its state (from closed to open) is hour j. If customers.charAt(j) == 'Y', the penalty decreases by 1. If customers.charAt(j) == 'N', the penalty increases by 1. This gives us the relation: penalty(j+1) = penalty(j) + (customers.charAt(j) == 'N' ? 1 : -1). The algorithm starts by calculating the initial penalty for closing at hour 0 (total 'Y's). Then, it iterates from j = 1 to n, updating the currentPenalty using the rule above and tracking the minimum penalty and corresponding hour.
class Solution { public int bestClosingTime(String customers) { int n = customers.length(); // Calculate initial penalty for closing at hour 0 (all hours are closed) // This is simply the count of all 'Y's. int currentPenalty = 0; for (char c : customers.toCharArray()) { if (c == 'Y') { currentPenalty++; } } int minPenalty = currentPenalty; int bestHour = 0; // Iterate through all possible closing hours from 1 to n for (int j = 1; j <= n; j++) { // Adjust penalty based on the customer at hour j-1, which is now open char customer = customers.charAt(j - 1); if (customer == 'Y') { currentPenalty--; // This hour is now open, so we lose a penalty point } else { // customer == 'N' currentPenalty++; // This hour is now open, so we gain a penalty point } if (currentPenalty < minPenalty) { minPenalty = currentPenalty; bestHour = j; } } return bestHour; }}Complexity
Time
O(n). We have an initial pass to count 'Y's (which can be combined into one loop) and then a single loop through the string, resulting in linear time complexity.
Space
O(1). We only use a few variables to keep track of the current penalty, minimum penalty, and best hour, regardless of the input size.
Trade-offs
Pros
Optimal solution with linear time and constant space complexity.
Highly efficient for very large inputs.
Cons
The logic for updating the penalty might be slightly less intuitive than the prefix/suffix sum approach at first glance.
Solutions
Solution
class Solution {public int bestClosingTime(String customers) { int n = customers.length(); int[] s = new int[n + 1]; for (int i = 0; i < n; ++i) { s[i + 1] = s[i] + (customers.charAt(i) == 'Y' ? 1 : 0); } int ans = 0, cost = 1 << 30; for (int j = 0; j <= n; ++j) { int t = j - s[j] + s[n] - s[j]; if (cost > t) { ans = j; cost = t; } } return ans; }}class Solution {public: int bestClosingTime(string customers) { int n = customers.size(); vector<int> s(n + 1); for (int i = 0; i < n; ++i) { s[i + 1] = s[i] + (customers[i] == 'Y'); } int ans = 0, cost = 1 << 30; for (int j = 0; j <= n; ++j) { int t = j - s[j] + s[n] - s[j]; if (cost > t) { ans = j; cost = t; } } return ans; }};class Solution: def bestClosingTime(self, customers: str) -> int: n = len(customers) s = [0] * (n + 1) for i, c in enumerate(customers): s[i + 1] = s[i] + int(c == 'Y') ans, cost = 0, inf for j in range(n + 1): t = j - s[j] + s[- 1] - s[j] if cost > t: ans, cost = j, t return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.