Plates Between Candles
MedPrompt
There is a long table with a line of plates and candles arranged on top of it. You are given a 0-indexed string s consisting of characters '*' and '|' only, where a '*' represents a plate and a '|' represents a candle.
You are also given a 0-indexed 2D integer array queries where queries[i] = [lefti, righti] denotes the substring s[lefti...righti] (inclusive). For each query, you need to find the number of plates between candles that are in the substring. A plate is considered between candles if there is at least one candle to its left and at least one candle to its right in the substring.
- For example,
s = "||**||**|*", and a query[3, 8]denotes the substring"*||**|". The number of plates between candles in this substring is2, as each of the two plates has at least one candle in the substring to its left and right.
Return an integer array answer where answer[i] is the answer to the ith query.
Example 1:
Input: s = "**|**|***|", queries = [[2,5],[5,9]]
Output: [2,3]
Explanation:
- queries[0] has two plates between candles.
- queries[1] has three plates between candles.Example 2:
Input: s = "***|**|*****|**||**|*", queries = [[1,17],[4,5],[14,17],[5,11],[15,16]]
Output: [9,0,0,0,0]
Explanation:
- queries[0] has nine plates between candles.
- The other queries have zero plates between candles.
Constraints:
3 <= s.length <= 105sconsists of'*'and'|'characters.1 <= queries.length <= 105queries[i].length == 20 <= lefti <= righti < s.length
Approaches
3 approaches with complexity analysis and trade-offs.
The most efficient approach involves pre-calculating all the necessary information to answer each query in constant time. We can precompute not only the prefix sums of plates but also the positions of the nearest candles for every index in the string.
Algorithm
- First, perform a full precomputation step in O(N) time:
- Create a prefix sum array
prefixPlatesof sizen.prefixPlates[i]stores the count of'*'ins[0...i]. - Create an array
prevCandleof sizen.prevCandle[i]stores the index of the closest candle to the left of or at indexi. This is computed in a single pass from left to right. - Create an array
nextCandleof sizen.nextCandle[i]stores the index of the closest candle to the right of or at indexi. This is computed in a single pass from right to left.
- Create a prefix sum array
- Initialize an
answerarray. - For each query
[left, right]:- Find the effective left boundary candle index:
startCandle = nextCandle[left]. - Find the effective right boundary candle index:
endCandle = prevCandle[right]. - Check if these boundaries are valid and if
startCandle < endCandle. - If they are, the number of plates is
prefixPlates[endCandle] - prefixPlates[startCandle]. This is an O(1) operation. - If the boundaries are not valid, the answer is 0.
- Find the effective left boundary candle index:
- Return the
answerarray.
Walkthrough
This approach achieves the optimal time complexity by performing a thorough precomputation. The key idea is to answer every query in constant time by having all the required information readily available in lookup tables (arrays).
We create three auxiliary arrays:
prefixPlates: As in the previous approach,prefixPlates[i]stores the cumulative number of plates up to indexi.nextCandle: For each indexi,nextCandle[i]stores the index of the first candle encountered when moving fromito the right. This can be computed with a single pass from right to left.prevCandle: For each indexi,prevCandle[i]stores the index of the first candle encountered when moving fromito the left. This is computed with a pass from left to right.
After this O(N) precomputation, each query [left, right] can be resolved instantly. The true left boundary is nextCandle[left], and the true right boundary is prevCandle[right]. If these boundaries are valid, the plate count is a simple lookup and subtraction using the prefixPlates array.
class Solution { public int[] platesBetweenCandles(String s, int[][] queries) { int n = s.length(); int[] prefixPlates = new int[n]; int count = 0; for (int i = 0; i < n; i++) { if (s.charAt(i) == '*') { count++; } prefixPlates[i] = count; } int[] prevCandle = new int[n]; int lastCandle = -1; for (int i = 0; i < n; i++) { if (s.charAt(i) == '|') { lastCandle = i; } prevCandle[i] = lastCandle; } int[] nextCandle = new int[n]; int firstCandle = -1; for (int i = n - 1; i >= 0; i--) { if (s.charAt(i) == '|') { firstCandle = i; } nextCandle[i] = firstCandle; } int[] ans = new int[queries.length]; for (int i = 0; i < queries.length; i++) { int left = queries[i][0]; int right = queries[i][1]; int startCandle = nextCandle[left]; int endCandle = prevCandle[right]; if (startCandle != -1 && endCandle != -1 && startCandle < endCandle) { ans[i] = prefixPlates[endCandle] - prefixPlates[startCandle]; } else { ans[i] = 0; } } return ans; }}Complexity
Time
O(N + Q). The precomputation takes three passes over the string, which is O(N). Each of the Q queries is then answered in O(1) time.
Space
O(N), where N is the string length. We use three arrays of size N for precomputation.
Trade-offs
Pros
Extremely fast, with constant time per query.
This is the most optimal solution in terms of time complexity.
Cons
Requires more space than other approaches due to the three helper arrays.
This is the most straightforward approach. For each query, we iterate through the given substring to find the boundaries (the first and last candles) and then count the plates between them.
Algorithm
- Initialize an integer array
answerwith the same size asqueries. - Iterate through each query
[left, right]in thequeriesarray. - For each query, find the index of the first candle within the range
[left, right]. Let's call itfirstCandle. If no candle is found, the answer for this query is 0. - Similarly, find the index of the last candle within the range
[left, right]. Let's call itlastCandle. - If
firstCandleis not found orfirstCandleis at or afterlastCandle, the number of plates between candles is 0. - Otherwise, iterate from
firstCandle + 1tolastCandle - 1. - Count the number of plate characters
'*'in this inner range. - Store the count in the
answerarray for the current query. - After processing all queries, return the
answerarray.
Walkthrough
For every query [left, right], we first need to identify the actual range of plates that are enclosed by candles within the substring s[left...right]. This involves a linear scan from left to right to find the index of the first candle, firstCandle, and another scan (or a continuation of the first) to find the index of the last candle, lastCandle. If we find at least two candles such that firstCandle < lastCandle, we then perform another iteration, this time from firstCandle to lastCandle, to count all the '*' characters. This count is the answer for the current query. If fewer than two candles are found in the range, the answer is 0. This process is repeated for every single query.
class Solution { public int[] platesBetweenCandles(String s, int[][] queries) { int q = queries.length; int[] ans = new int[q]; for (int i = 0; i < q; i++) { int left = queries[i][0]; int right = queries[i][1]; int firstCandle = -1; for (int j = left; j <= right; j++) { if (s.charAt(j) == '|') { firstCandle = j; break; } } if (firstCandle == -1) { ans[i] = 0; continue; } int lastCandle = -1; // We can optimize this by just continuing the scan, but for clarity a separate loop is shown. for (int j = right; j >= firstCandle; j--) { if (s.charAt(j) == '|') { lastCandle = j; break; } } if (lastCandle == -1 || firstCandle >= lastCandle) { ans[i] = 0; continue; } int count = 0; for (int j = firstCandle + 1; j < lastCandle; j++) { if (s.charAt(j) == '*') { count++; } } ans[i] = count; } return ans; }}Complexity
Time
O(Q * N), where Q is the number of queries and N is the length of the string `s`. For each query, we might scan the substring, which can have a length of up to N.
Space
O(Q) or O(1), where Q is the number of queries. The space is dominated by the output array. Excluding the output, the space complexity is O(1).
Trade-offs
Pros
Simple to understand and implement.
Requires minimal extra space.
Cons
Highly inefficient due to repeated scanning of the string.
Will result in a 'Time Limit Exceeded' error on platforms like LeetCode for the given constraints.
To optimize the brute-force approach, we can precompute the locations of all candles. For each query, we can then use binary search to quickly find the relevant candles that bound the plates, and a prefix sum array to count the plates between them in constant time.
Algorithm
- First, perform a precomputation step:
- Create a list,
candleIndices, and populate it with the indices of all'|'characters ins. - Create a prefix sum array,
prefixPlates, of sizen(wherenis the length ofs).prefixPlates[i]will store the count of'*'in the substrings[0...i].
- Create a list,
- Initialize an
answerarray. - For each query
[left, right]:- Use binary search on
candleIndicesto find the index of the first candle at or afterleft. This is thelower_boundofleft. - Use binary search on
candleIndicesto find the index of the last candle at or beforeright. This can be found by searching for theupper_boundofrightand taking the previous index. - If valid left and right boundary candles are found and the left candle comes before the right one:
- Retrieve their actual string indices,
leftCandlePosandrightCandlePos. - The number of plates is calculated in O(1) using the prefix sum array:
prefixPlates[rightCandlePos] - prefixPlates[leftCandlePos].
- Retrieve their actual string indices,
- Otherwise, the answer is 0.
- Use binary search on
- Return the
answerarray.
Walkthrough
This approach improves upon the brute force method by avoiding repeated scans of the string. We start by pre-processing the input string s in two ways. First, we create a list containing the indices of all candles. This allows us to focus only on the candle positions. Second, we create a prefix sum array that stores, for each index i, the cumulative count of plates up to that index.
With these precomputed structures, we can process each query [left, right] much faster. We use binary search on our list of candle indices to find the effective boundaries for our plate count. Specifically, we find the first candle that is not before left and the last candle that is not after right. Once we have the indices of these two boundary candles, we can use our prefix sum array to find the number of plates between them in a single subtraction operation. This reduces the work per query from linear to logarithmic time.
import java.util.ArrayList;import java.util.Collections;import java.util.List; class Solution { public int[] platesBetweenCandles(String s, int[][] queries) { int n = s.length(); List<Integer> candleIndices = new ArrayList<>(); int[] prefixPlates = new int[n]; int plateCount = 0; for (int i = 0; i < n; i++) { if (s.charAt(i) == '*') { plateCount++; } else { candleIndices.add(i); } prefixPlates[i] = plateCount; } int[] ans = new int[queries.length]; if (candleIndices.isEmpty()) { return ans; // All zeros if no candles } for (int i = 0; i < queries.length; i++) { int left = queries[i][0]; int right = queries[i][1]; // Find first candle index >= left (lower_bound) int startIdx = Collections.binarySearch(candleIndices, left); if (startIdx < 0) { startIdx = -(startIdx + 1); } // Find last candle index <= right (upper_bound - 1) int endIdx = Collections.binarySearch(candleIndices, right); if (endIdx < 0) { endIdx = -(endIdx + 1) - 1; } if (startIdx < candleIndices.size() && endIdx >= 0 && startIdx < endIdx) { int leftCandlePos = candleIndices.get(startIdx); int rightCandlePos = candleIndices.get(endIdx); ans[i] = prefixPlates[rightCandlePos] - prefixPlates[leftCandlePos]; } } return ans; }}Complexity
Time
O(N + Q * log C), where N is the string length, Q is the number of queries, and C is the number of candles. Precomputation takes O(N). Each query involves two binary searches on the list of candle indices, taking O(log C) time.
Space
O(N), where N is the string length. We need O(C) space for `candleIndices` (where C is the number of candles, C <= N) and O(N) for `prefixPlates`.
Trade-offs
Pros
Significantly faster than the brute-force approach for a large number of queries.
Efficiently handles sparse or dense distributions of candles.
Cons
The logarithmic time complexity per query makes it slightly slower than the optimal linear time solution.
Implementation of binary search to find the correct boundaries requires careful handling of edge cases.
Solutions
Solution
class Solution {public int[] platesBetweenCandles(String s, int[][] queries) { int n = s.length(); int[] presum = new int[n + 1]; for (int i = 0; i < n; ++i) { presum[i + 1] = presum[i] + (s.charAt(i) == '*' ? 1 : 0); } int[] left = new int[n]; int[] right = new int[n]; for (int i = 0, l = -1; i < n; ++i) { if (s.charAt(i) == '|') { l = i; } left[i] = l; } for (int i = n - 1, r = -1; i >= 0; --i) { if (s.charAt(i) == '|') { r = i; } right[i] = r; } int[] ans = new int[queries.length]; for (int k = 0; k < queries.length; ++k) { int i = right[queries[k][0]]; int j = left[queries[k][1]]; if (i >= 0 && j >= 0 && i < j) { ans[k] = presum[j] - presum[i + 1]; } } return ans; }}class Solution {public: vector<int> platesBetweenCandles(string s, vector<vector<int>> &queries) { int n = s.size(); vector<int> presum(n + 1); for (int i = 0; i < n; ++i) presum[i + 1] = presum[i] + (s[i] == '*'); vector<int> left(n); vector<int> right(n); for (int i = 0, l = -1; i < n; ++i) { if (s[i] == '|') l = i; left[i] = l; } for (int i = n - 1, r = -1; i >= 0; --i) { if (s[i] == '|') r = i; right[i] = r; } vector<int> ans(queries.size()); for (int k = 0; k < queries.size(); ++k) { int i = right[queries[k][0]]; int j = left[queries[k][1]]; if (i >= 0 && j >= 0 && i < j) ans[k] = presum[j] - presum[i + 1]; } return ans; }};class Solution: def platesBetweenCandles(self, s: str, queries: List[List[int]]) -> List[int]: n = len(s) presum = [0] * (n + 1) for i, c in enumerate(s): presum[i + 1] = presum[i] + (c == '*') left, right = [0] * n, [0] * n l = r = - 1 for i, c in enumerate(s): if c == '|': l = i left[i] = l for i in range(n - 1, - 1, - 1): if s[i] == '|': r = i right[i] = r ans = [0] * len(queries) for k, (l, r) in enumerate(queries): i, j = right[l], left[r] if i >= 0 and j >= 0 and i < j: ans[k] = presum[j] - presum[i + 1] 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.