3481. Apply Substitutions 🔒
Problem Description
You are given a replacements
mapping and a text
string that may contain placeholders formatted as %var%
, where each var
corresponds to a key in the replacements
mapping. Each replacement value may itself contain one or more such placeholders. Each placeholder is replaced by the value associated with its corresponding replacement key.
Return the fully substituted text
string which does not contain any placeholders.
Intuition
The problem involves replacing placeholders in a given text with corresponding values from a mapping, where these values can themselves contain further placeholders. This recursive nature suggests an approach using depth-first search (DFS) to fully resolve all placeholders.
The solution involves a couple of key steps:
-
Mapping Storage: Use a hash table to store the mapping between placeholder names and their substitution values.
-
Recursive Replacement: Implement a recursive function (
dfs
) that searches for placeholders in the string. Each time it finds a placeholder, it extracts the key, looks up its value in the mapping, and replaces the placeholder with this value. -
Complete Resolution: The recursion ensures that if a replacement itself contains placeholders, it will be further processed until no placeholders remain.
The main idea is to apply these substitutions until the text is entirely resolved without placeholders.
Learn more about Depth-First Search, Breadth-First Search, Graph and Topological Sort patterns.
Solution Approach
To solve this problem, we utilize a hash table combined with recursion to efficiently handle and replace the placeholders in the given text
string.
Here's a detailed walkthrough of the solution approach:
-
Hash Table for Mapping: We create a hash table
d
from thereplacements
, where each key-value pair corresponds to a placeholder and its replacement string. This enables quick lookups during the substitution process.d = {s: t for s, t in replacements}
-
Recursive Function (
dfs
): We define a recursive functiondfs
that processes the strings
:-
Find Placeholders: Locate the first occurrence of
%
to determine the start and end of a placeholder within the string.- Use
s.find("%")
to find the starting positioni
of a placeholder. - Use
s.find("%", i + 1)
to find the ending positionj
of the same placeholder.
- Use
-
Extract and Replace: Extract the key using the substring between these positions (
s[i + 1 : j]
). Recursively calldfs
to resolve the replacement string, which accounts for further nested placeholders.key = s[i + 1 : j] replacement = dfs(d[key])
-
Combine Results: Construct the final string by concatenating the resolved portion before the placeholder, the resolved replacement, and calling
dfs
for the remaining part of the string after the placeholder.return s[:i] + replacement + dfs(s[j + 1 :])
-
-
Initiate Replacement: The main function
applySubstitutions
starts the recursive process with the initialtext
string to resolve all placeholders.
By iterating recursively and leveraging the mapping efficiently, this approach ensures that all nested and complex placeholders are accurately replaced, ultimately providing a fully substituted string.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach with a small example:
Imagine we have the replacements
mapping as follows:
replacements = [ ("name", "John"), ("greeting", "Hello, %name%"), ("farewell", "Goodbye, %name%") ]
And suppose we have a text
string:
"%greeting%! How are you? %farewell%!"
Here's how the solution approach would work, step by step:
-
Hash Table Creation: We start by converting the
replacements
list into a hash tabled
for quick lookups:d = { "name": "John", "greeting": "Hello, %name%", "farewell": "Goodbye, %name%" }
-
Begin Recursive Replacement: We call the
dfs
function on the initialtext
string:- The first placeholder
%greeting%
is located in the string. - Inside
dfs
, we extract the key, which isgreeting
, and use it to retrieve the value"Hello, %name%"
fromd
.
- The first placeholder
-
Nested Placeholder Resolution: The retrieved string
"Hello, %name%"
contains another placeholder%name%
.- We recursively process
%name%
usingdfs
. - For
%name%
, the value"John"
is obtained fromd
.
- We recursively process
-
Combining Solutions: Once the innermost placeholder
%name%
is replaced with"John"
, the greeting part becomes"Hello, John"
.- We continue to resolve the rest of the original
text
by substituting%greeting%
with"Hello, John"
, resulting in the intermediate outcome:"Hello, John! How are you? %farewell%!"
.
- We continue to resolve the rest of the original
-
Resolve Remaining Placeholders: Next, we perform a similar process to replace
%farewell%
:%farewell%
is found to be"Goodbye, %name%"
.- Again,
%name%
resolves to"John"
, leading to"Goodbye, John"
.
-
Final Output: The fully substituted string after resolving all placeholders becomes:
"Hello, John! How are you? Goodbye, John!"
Through this recursive process, every placeholder is iteratively resolved, ensuring the output string is free of any unresolved placeholders.
Solution Implementation
1from typing import List
2
3class Solution:
4 def applySubstitutions(self, replacements: List[List[str]], text: str) -> str:
5 # Depth-first search function to substitute keys with their corresponding replacements
6 def dfs(current_text: str) -> str:
7 # Find the first occurrence of a '%' character
8 start_index = current_text.find("%")
9 # If no '%' is found, return the unmodified text
10 if start_index == -1:
11 return current_text
12 # Find the next '%' character after the first one
13 end_index = current_text.find("%", start_index + 1)
14 # If the closing '%' is not found, return the unmodified text
15 if end_index == -1:
16 return current_text
17 # Extract the key between the '%' characters
18 key = current_text[start_index + 1: end_index]
19 # Perform replacement using the dictionary and recursively apply substitutions
20 replacement = dfs(substitute_map.get(key, key))
21 # Concatenate the parts and the replacement, and apply dfs on the rest
22 return current_text[:start_index] + replacement + dfs(current_text[end_index + 1:])
23
24 # Create a dictionary from the list of replacement pairs
25 substitute_map = {s: t for s, t in replacements}
26 # Return the processed text after substitutions
27 return dfs(text)
28
1import java.util.HashMap;
2import java.util.List;
3import java.util.Map;
4
5class Solution {
6 private final Map<String, String> substitutionMap = new HashMap<>();
7
8 // Applies substitutions to the given text based on the list of replacements provided.
9 public String applySubstitutions(List<List<String>> replacements, String text) {
10 // Populate the substitution map with keys and their corresponding replacements.
11 for (List<String> pair : replacements) {
12 substitutionMap.put(pair.get(0), pair.get(1));
13 }
14 // Start the recursive substitution process.
15 return dfs(text);
16 }
17
18 // Recursive function to process the text and substitute placeholders with their corresponding values.
19 private String dfs(String str) {
20 // Find the first occurrence of '%'.
21 int startIndex = str.indexOf("%");
22 // If no placeholder exists, return the string as it is.
23 if (startIndex == -1) {
24 return str;
25 }
26
27 // Find the matching closing '%' for the placeholder.
28 int endIndex = str.indexOf("%", startIndex + 1);
29 // If the closing '%' is not found, return the string as it is.
30 if (endIndex == -1) {
31 return str;
32 }
33
34 // Extract the key located between the '%' signs.
35 String key = str.substring(startIndex + 1, endIndex);
36 // Recursively resolve the key into its final replacement value.
37 String replacement = dfs(substitutionMap.getOrDefault(key, ""));
38
39 // Construct the new string with the resolved replacement and continue processing the rest of the text.
40 return str.substring(0, startIndex) + replacement + dfs(str.substring(endIndex + 1));
41 }
42}
43
1#include <string>
2#include <vector>
3#include <unordered_map>
4using namespace std;
5
6class Solution {
7public:
8 string applySubstitutions(vector<vector<string>>& replacements, string text) {
9 // Create a hash map for replacements
10 unordered_map<string, string> dictionary;
11 for (const auto& pair : replacements) {
12 dictionary[pair[0]] = pair[1]; // Store the substitutions
13 }
14
15 // Define a recursive lambda function to perform depth-first substitution
16 auto dfs = [&](auto&& dfs_ref, const string& current) -> string {
17 size_t start_index = current.find('%');
18 if (start_index == string::npos) {
19 return current; // No more substitutions found
20 }
21
22 size_t end_index = current.find('%', start_index + 1);
23 if (end_index == string::npos) {
24 return current; // No valid closing '%' for substitution
25 }
26
27 // Extract the key between '%' characters
28 string key = current.substr(start_index + 1, end_index - start_index - 1);
29 // Retrieve replacement from dictionary, if it exists
30 string replacement = dfs_ref(dfs_ref, dictionary[key]);
31 // Recur for the rest of the string after performing substitution
32 return current.substr(0, start_index) + replacement + dfs_ref(dfs_ref, current.substr(end_index + 1));
33 };
34
35 // Start the recursive substitution from the original text
36 return dfs(dfs, text);
37 }
38};
39
1// Function to apply substitutions in a given text based on a list of replacements
2function applySubstitutions(replacements: string[][], text: string): string {
3 // Create a dictionary to map keys to their replacement values
4 const d: Record<string, string> = {};
5 for (const [key, value] of replacements) {
6 d[key] = value;
7 }
8
9 // Helper function to recursively process the text and apply substitutions
10 const dfs = (s: string): string => {
11 // Find the index of the first occurrence of '%'
12 const i = s.indexOf('%');
13 if (i === -1) {
14 return s; // Return the string if no '%' is found
15 }
16 // Find the index of the next occurrence of '%'
17 const j = s.indexOf('%', i + 1);
18 if (j === -1) {
19 return s; // Return the string if no matching '%' is found
20 }
21 // Extract the key between the '%' symbols
22 const key = s.slice(i + 1, j);
23 // Recursively retrieve the replacement value for the key, or an empty string if not found
24 const replacement = dfs(d[key] ?? '');
25 // Reconstruct the string with the replacement and continue processing the rest
26 return s.slice(0, i) + replacement + dfs(s.slice(j + 1));
27 };
28
29 // Start the substitution process on the input text
30 return dfs(text);
31}
32
Time and Space Complexity
The code performs substitutions recursively using depth-first search (DFS):
-
Time Complexity:
- There are three main operations contributing to the time complexity:
- Finding the next pair of
%
characters ins
usings.find()
, which takesO(n)
time in the worst case. - Constructing the dictionary
d
from the replacements list takesO(m)
, wherem
is the number of substitutions. - Substituting the key in the string requires recursive calls. This involves traversing and replacing parts of the string, potentially visiting each character multiple times depending on the nested structure of substitutions.
- Finding the next pair of
Therefore, the time complexity is
O(m + n * L)
, wheren
is the length of the text string andL
is the average length of the placeholder keys. - There are three main operations contributing to the time complexity:
-
Space Complexity:
O(m)
space for the dictionaryd
to store the substitutions.O(n * L)
space for the call stack due to recursion, which occurs due to potential nested placeholders.O(n * L)
space for building the resultant string as we might replace each%key%
with its respective value fromd
.
The overall space complexity is
O(m + n * L)
.
Learn more about how to find time and space complexity quickly.
How many times is a tree node visited in a depth first search?
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Want a Structured Path to Master System Design Too? Don’t Miss This!