Facebook Pixel

1807. Evaluate the Bracket Pairs of a String

MediumArrayHash TableString
Leetcode Link

Problem Description

You are given a string s containing bracket pairs, where each pair encloses a non-empty key. For example, in "(name)is(age)yearsold", there are two bracket pairs containing the keys "name" and "age".

You also have a 2D array knowledge where each element knowledge[i] = [keyi, valuei] represents that keyi has a value of valuei. This acts as a dictionary of known key-value pairs.

Your task is to evaluate all bracket pairs in the string by:

  • If a key inside brackets exists in your knowledge, replace the entire bracket pair (including the brackets) with the corresponding value
  • If a key is not found in your knowledge, replace the entire bracket pair with a question mark "?"

Important constraints:

  • Each key appears at most once in the knowledge array
  • There are no nested brackets in the string s
  • All bracket pairs contain non-empty keys

For example:

  • Input: s = "(name)is(age)yearsold", knowledge = [["name","bob"],["age","two"]]
  • Output: "bobistwoyearsold"

The solution uses a hash table to store the knowledge pairs for O(1) lookup. It then iterates through the string, and whenever it encounters an opening bracket '(', it finds the corresponding closing bracket ')', extracts the key between them, looks up the value in the hash table, and appends either the found value or '?' to the result. Characters outside brackets are appended as-is.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The problem is essentially a string replacement task where we need to substitute placeholders (keys in brackets) with their corresponding values. This immediately suggests using a hash table for fast lookups.

When we see bracket pairs like "(name)" and "(age)" that need to be replaced with values from a knowledge base, the natural approach is to:

  1. First, convert the knowledge array into a more accessible format - a hash table/dictionary allows O(1) lookups instead of scanning through the array each time we need a value.

  2. Process the string character by character. When we encounter normal characters (not brackets), we keep them as-is. When we hit an opening bracket '(', we know a key starts right after it and ends at the closing bracket ')'.

  3. The key insight is that we can use string's built-in find() method to locate the closing bracket, then extract the substring between the brackets as our key. This is more efficient than manually scanning character by character.

  4. Once we have the key, we look it up in our hash table. The get() method with a default value of '?' elegantly handles both cases - returning the value if the key exists, or '?' if it doesn't.

  5. After processing each bracket pair or regular character, we move our pointer forward appropriately - jumping past the entire bracket pair when we process one, or moving one character at a time for regular characters.

The beauty of this approach is its simplicity - we're doing a single pass through the string with O(1) lookups for each bracket pair, making it both time and space efficient.

Solution Approach

The implementation follows a straightforward simulation approach using a hash table for efficient lookups:

  1. Build the Hash Table: First, we convert the knowledge array into a dictionary d using a dictionary comprehension: d = {a: b for a, b in knowledge}. This transforms the 2D array into a key-value mapping for O(1) access time.

  2. Initialize Variables: We set up:

    • i = 0: pointer to track our current position in the string
    • n = len(s): the length of the string for boundary checking
    • ans = []: a list to build our result string (more efficient than string concatenation)
  3. Main Processing Loop: We iterate through the string with while i < n:

    • Case 1 - Opening Bracket Found (s[i] == '('):
      • Use s.find(')', i + 1) to locate the closing bracket position j
      • Extract the key: s[i + 1 : j] (substring between the brackets, excluding them)
      • Look up the key in dictionary d using d.get(s[i + 1 : j], '?'):
        • If the key exists, return its value
        • If not, return the default '?'
      • Append the result to ans
      • Update pointer: i = j to skip to the closing bracket position
    • Case 2 - Regular Character:
      • Simply append the current character s[i] to ans
    • After processing either case, increment i += 1 to move forward
  4. Return Result: Join all elements in ans list into a final string using ''.join(ans).

The time complexity is O(n + m) where n is the length of string s and m is the number of entries in knowledge. The space complexity is O(m) for storing the hash table, plus O(n) for the result list.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through a small example to illustrate the solution approach:

Input: s = "(a)and(b)", knowledge = [["a","hello"],["c","world"]]

Step 1: Build the Hash Table

  • Convert knowledge into dictionary: d = {"a": "hello", "c": "world"}

Step 2: Initialize Variables

  • i = 0 (starting position)
  • n = 9 (length of "(a)and(b)")
  • ans = [] (empty result list)

Step 3: Process the String

Iteration 1 (i=0):

  • s[0] = '(' - Opening bracket found!
  • Find closing bracket: j = s.find(')', 1) = 2
  • Extract key: s[1:2] = "a"
  • Lookup in dictionary: d.get("a", '?') = "hello" (key exists!)
  • Append "hello" to ans: ans = ["hello"]
  • Update position: i = 2
  • Increment: i = 3

Iteration 2 (i=3):

  • s[3] = 'a' - Regular character
  • Append 'a' to ans: ans = ["hello", "a"]
  • Increment: i = 4

Iteration 3 (i=4):

  • s[4] = 'n' - Regular character
  • Append 'n' to ans: ans = ["hello", "a", "n"]
  • Increment: i = 5

Iteration 4 (i=5):

  • s[5] = 'd' - Regular character
  • Append 'd' to ans: ans = ["hello", "a", "n", "d"]
  • Increment: i = 6

Iteration 5 (i=6):

  • s[6] = '(' - Opening bracket found!
  • Find closing bracket: j = s.find(')', 7) = 8
  • Extract key: s[7:8] = "b"
  • Lookup in dictionary: d.get("b", '?') = '?' (key not found!)
  • Append '?' to ans: ans = ["hello", "a", "n", "d", "?"]
  • Update position: i = 8
  • Increment: i = 9

Step 4: Return Result

  • Join the list: ''.join(ans) = "helloand?"

Output: "helloand?"

The key "(a)" was replaced with "hello" since it exists in our knowledge, while "(b)" was replaced with "?" since it's not in our knowledge base.

Solution Implementation

1class Solution:
2    def evaluate(self, s: str, knowledge: List[List[str]]) -> str:
3        # Convert knowledge list to dictionary for O(1) lookup
4        # Each sublist [key, value] becomes a key-value pair
5        knowledge_dict = {key: value for key, value in knowledge}
6      
7        # Initialize pointer and get string length
8        index = 0
9        string_length = len(s)
10      
11        # Result list to build the final string efficiently
12        result = []
13      
14        # Process each character in the string
15        while index < string_length:
16            if s[index] == '(':
17                # Found opening bracket, find the corresponding closing bracket
18                closing_bracket_index = s.find(')', index + 1)
19              
20                # Extract the key between brackets (excluding the brackets themselves)
21                key = s[index + 1 : closing_bracket_index]
22              
23                # Look up the key in dictionary, use '?' if key not found
24                replacement_value = knowledge_dict.get(key, '?')
25                result.append(replacement_value)
26              
27                # Move pointer to the closing bracket position
28                index = closing_bracket_index
29            else:
30                # Regular character, add it directly to result
31                result.append(s[index])
32          
33            # Move to next character
34            index += 1
35      
36        # Join all parts into final string
37        return ''.join(result)
38
1class Solution {
2    public String evaluate(String s, List<List<String>> knowledge) {
3        // Create a HashMap to store key-value pairs from knowledge base
4        // Each entry maps a key (first element) to its value (second element)
5        Map<String, String> knowledgeMap = new HashMap<>(knowledge.size());
6      
7        // Populate the map with knowledge pairs
8        for (List<String> pair : knowledge) {
9            knowledgeMap.put(pair.get(0), pair.get(1));
10        }
11      
12        // StringBuilder to efficiently build the result string
13        StringBuilder result = new StringBuilder();
14      
15        // Iterate through each character in the input string
16        for (int i = 0; i < s.length(); i++) {
17            if (s.charAt(i) == '(') {
18                // Found opening bracket, find the corresponding closing bracket
19                int closingBracketIndex = s.indexOf(')', i + 1);
20              
21                // Extract the key between brackets (excluding the brackets themselves)
22                String key = s.substring(i + 1, closingBracketIndex);
23              
24                // Replace the bracketed expression with its value from the map
25                // If key doesn't exist, use "?" as default
26                result.append(knowledgeMap.getOrDefault(key, "?"));
27              
28                // Move the index to the closing bracket position
29                // (loop will increment it by 1 in the next iteration)
30                i = closingBracketIndex;
31            } else {
32                // Regular character, append it directly to the result
33                result.append(s.charAt(i));
34            }
35        }
36      
37        // Convert StringBuilder to String and return
38        return result.toString();
39    }
40}
41
1class Solution {
2public:
3    string evaluate(string s, vector<vector<string>>& knowledge) {
4        // Build a hash map for O(1) lookup of key-value pairs
5        unordered_map<string, string> knowledgeMap;
6        for (auto& pair : knowledge) {
7            knowledgeMap[pair[0]] = pair[1];
8        }
9      
10        string result;
11      
12        // Iterate through the input string
13        for (int i = 0; i < s.size(); ++i) {
14            if (s[i] == '(') {
15                // Found opening bracket, find the matching closing bracket
16                int closingBracketPos = s.find(")", i + 1);
17              
18                // Extract the key between brackets (excluding the brackets)
19                string key = s.substr(i + 1, closingBracketPos - i - 1);
20              
21                // Replace the bracketed expression with its value or "?" if not found
22                result += knowledgeMap.count(key) ? knowledgeMap[key] : "?";
23              
24                // Move index to the closing bracket position to skip processed part
25                i = closingBracketPos;
26            } else {
27                // Regular character, append directly to result
28                result += s[i];
29            }
30        }
31      
32        return result;
33    }
34};
35
1/**
2 * Evaluates a string by replacing bracketed keys with their corresponding values from knowledge base
3 * @param s - The input string containing text and bracketed keys to be replaced
4 * @param knowledge - A 2D array where each sub-array contains [key, value] pairs
5 * @returns The evaluated string with all bracketed keys replaced by their values or '?' if not found
6 */
7function evaluate(s: string, knowledge: string[][]): string {
8    const stringLength: number = s.length;
9  
10    // Create a map from the knowledge array for O(1) lookup
11    const knowledgeMap: Map<string, string> = new Map();
12    for (const [key, value] of knowledge) {
13        knowledgeMap.set(key, value);
14    }
15  
16    // Result array to build the final string
17    const result: string[] = [];
18    let currentIndex: number = 0;
19  
20    // Process the string character by character
21    while (currentIndex < stringLength) {
22        if (s[currentIndex] === '(') {
23            // Found opening bracket, find the corresponding closing bracket
24            const closingBracketIndex: number = s.indexOf(')', currentIndex + 1);
25          
26            // Extract the key between brackets
27            const key: string = s.slice(currentIndex + 1, closingBracketIndex);
28          
29            // Look up the value in the map, use '?' if not found
30            result.push(knowledgeMap.get(key) ?? '?');
31          
32            // Move index to the closing bracket position
33            currentIndex = closingBracketIndex;
34        } else {
35            // Regular character, add it directly to the result
36            result.push(s[currentIndex]);
37        }
38      
39        // Move to the next character
40        currentIndex++;
41    }
42  
43    // Join the result array into a single string
44    return result.join('');
45}
46

Time and Space Complexity

Time Complexity: O(n + m)

The time complexity consists of two main parts:

  • Creating the dictionary from knowledge list: O(m), where m is the length of the knowledge list. Each key-value pair is inserted into the dictionary in constant time.
  • Traversing the string s: O(n), where n is the length of string s. Each character is visited exactly once. The find() operation for closing parenthesis searches forward from the current position, but since we skip to the found position afterwards, each character is still processed only once overall.

Space Complexity: O(L)

The space complexity is dominated by:

  • The dictionary d which stores all key-value pairs from knowledge: This requires O(L) space, where L is the sum of lengths of all strings in the knowledge list (both keys and values).
  • The answer list ans which in the worst case stores O(n) characters, but this is typically smaller than L.
  • Other variables like i, n, and j use constant space O(1).

Therefore, the overall space complexity is O(L) as it dominates the space usage.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Index Update After Finding Bracket

A critical pitfall occurs when updating the index after processing a bracketed key. The current code sets index = closing_bracket_index, which points to the ')' character. Then, the loop increments index += 1, correctly moving past the closing bracket. However, developers often make one of these mistakes:

Mistake A: Setting index = closing_bracket_index + 1 and keeping the index += 1 at the end of the loop, causing the character immediately after ')' to be skipped.

# WRONG - This skips the character after ')'
if s[index] == '(':
    closing_bracket_index = s.find(')', index + 1)
    key = s[index + 1 : closing_bracket_index]
    result.append(knowledge_dict.get(key, '?'))
    index = closing_bracket_index + 1  # Already moves past ')'
# ... 
index += 1  # This causes us to skip a character!

Mistake B: Forgetting to update the index at all, causing an infinite loop when a bracket is encountered.

Solution: Consistently set index = closing_bracket_index and rely on the single index += 1 at the end of the loop to move past the closing bracket.

2. String Concatenation Performance Issue

Using string concatenation with += operator instead of a list can lead to O(n²) time complexity:

# INEFFICIENT - String concatenation creates new string objects
result = ""
while index < string_length:
    if s[index] == '(':
        # ...
        result += replacement_value  # Creates new string each time
    else:
        result += s[index]  # Creates new string each time

Solution: Use a list to collect parts and join at the end, maintaining O(n) time complexity.

3. Missing Edge Case: Empty Keys

While the problem states "all bracket pairs contain non-empty keys", defensive programming suggests handling potential empty keys "()":

if s[index] == '(':
    closing_bracket_index = s.find(')', index + 1)
    if closing_bracket_index == index + 1:  # Empty key check
        result.append('?')
    else:
        key = s[index + 1 : closing_bracket_index]
        result.append(knowledge_dict.get(key, '?'))
    index = closing_bracket_index

4. Not Handling Missing Closing Bracket

If the input is malformed with an unclosed bracket, s.find(')') returns -1, causing incorrect slicing:

if s[index] == '(':
    closing_bracket_index = s.find(')', index + 1)
    if closing_bracket_index == -1:
        # Handle error: no closing bracket found
        result.append('?')
        index = string_length  # Exit loop
    else:
        key = s[index + 1 : closing_bracket_index]
        result.append(knowledge_dict.get(key, '?'))
        index = closing_bracket_index

These defensive checks make the code more robust against unexpected inputs while maintaining the core algorithm's efficiency.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More