1807. Evaluate the Bracket Pairs of a String
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.
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:
-
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.
-
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')'
. -
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. -
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. -
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:
-
Build the Hash Table: First, we convert the
knowledge
array into a dictionaryd
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. -
Initialize Variables: We set up:
i = 0
: pointer to track our current position in the stringn = len(s)
: the length of the string for boundary checkingans = []
: a list to build our result string (more efficient than string concatenation)
-
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 positionj
- Extract the key:
s[i + 1 : j]
(substring between the brackets, excluding them) - Look up the key in dictionary
d
usingd.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
- Use
- Case 2 - Regular Character:
- Simply append the current character
s[i]
toans
- Simply append the current character
- After processing either case, increment
i += 1
to move forward
- Case 1 - Opening Bracket Found (
-
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 EvaluatorExample 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)
, wherem
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)
, wheren
is the length of strings
. Each character is visited exactly once. Thefind()
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 requiresO(L)
space, whereL
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 storesO(n)
characters, but this is typically smaller thanL
. - Other variables like
i
,n
, andj
use constant spaceO(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.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!