Facebook Pixel

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:

  1. Mapping Storage: Use a hash table to store the mapping between placeholder names and their substitution values.

  2. 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.

  3. 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:

  1. Hash Table for Mapping: We create a hash table d from the replacements, 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}
  2. Recursive Function (dfs): We define a recursive function dfs that processes the string s:

    • 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 position i of a placeholder.
      • Use s.find("%", i + 1) to find the ending position j of the same placeholder.
    • Extract and Replace: Extract the key using the substring between these positions (s[i + 1 : j]). Recursively call dfs 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 :])
  3. Initiate Replacement: The main function applySubstitutions starts the recursive process with the initial text 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 Evaluator

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

  1. Hash Table Creation: We start by converting the replacements list into a hash table d for quick lookups:

    d = {
        "name": "John",
        "greeting": "Hello, %name%",
        "farewell": "Goodbye, %name%"
    }
  2. Begin Recursive Replacement: We call the dfs function on the initial text string:

    • The first placeholder %greeting% is located in the string.
    • Inside dfs, we extract the key, which is greeting, and use it to retrieve the value "Hello, %name%" from d.
  3. Nested Placeholder Resolution: The retrieved string "Hello, %name%" contains another placeholder %name%.

    • We recursively process %name% using dfs.
    • For %name%, the value "John" is obtained from d.
  4. 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%!".
  5. 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".
  6. 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):

  1. Time Complexity:

    • There are three main operations contributing to the time complexity:
      1. Finding the next pair of % characters in s using s.find(), which takes O(n) time in the worst case.
      2. Constructing the dictionary d from the replacements list takes O(m), where m is the number of substitutions.
      3. 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.

    Therefore, the time complexity is O(m + n * L), where n is the length of the text string and L is the average length of the placeholder keys.

  2. Space Complexity:

    • O(m) space for the dictionary d 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 from d.

    The overall space complexity is O(m + n * L).

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


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

How many times is a tree node visited in a depth first search?


Recommended Readings

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

Load More