Plates Between Candles

MEDIUM

Description

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 is 2, 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:

ex-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:

ex-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 <= 105
  • s consists of '*' and '|' characters.
  • 1 <= queries.length <= 105
  • queries[i].length == 2
  • 0 <= lefti <= righti < s.length

Approaches

Checkout 3 different approaches to solve Plates Between Candles. Click on different approaches to view the approach and algorithm in detail.

Full Precomputation with Helper Arrays

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:
    1. Create a prefix sum array prefixPlates of size n. prefixPlates[i] stores the count of '*' in s[0...i].
    2. Create an array prevCandle of size n. prevCandle[i] stores the index of the closest candle to the left of or at index i. This is computed in a single pass from left to right.
    3. Create an array nextCandle of size n. nextCandle[i] stores the index of the closest candle to the right of or at index i. This is computed in a single pass from right to left.
  • Initialize an answer array.
  • 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.
  • Return the answer array.

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:

  1. prefixPlates: As in the previous approach, prefixPlates[i] stores the cumulative number of plates up to index i.
  2. nextCandle: For each index i, nextCandle[i] stores the index of the first candle encountered when moving from i to the right. This can be computed with a single pass from right to left.
  3. prevCandle: For each index i, prevCandle[i] stores the index of the first candle encountered when moving from i to 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 Analysis

Time Complexity: 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 Complexity: O(N), where N is the string length. We use three arrays of size N for precomputation.

Pros and Cons

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.

Code Solutions

Checking out 3 solutions in different languages for Plates Between Candles. Click on different languages to view the code.

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;
  }
}

Video Solution

Watch the video walkthrough for Plates Between Candles



Algorithms:

Binary Search

Patterns:

Prefix Sum

Data Structures:

ArrayString

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.