You are given a string s
and a list of substitutions. Each substitution is a tuple (old, new)
where old
is the string to be replaced and new
is the string to replace it with. Apply the substitutions to the string s
in order. If multiple substitutions overlap, apply the one that starts earliest. If two overlapping substitutions start at the same index, prefer the longer old
string.
For example:
s = "banana"
, substitutions = [("ana", "xyz"), ("ban", "abc")]
should return "abcxyzna"
.s = "ababa"
, substitutions = [("aba", "c"), ("ab", "d")]
should return "cba"
.s = "leetcode"
, substitutions = [("leet", "code"), ("code", "leet")]
should return "codecode"
.s = "aaa"
, substitutions = [("a", "b"), ("aa", "c")]
should return "cba"
because "aa" is considered first.Describe an algorithm to efficiently apply these substitutions and return the modified string. What is the time and space complexity of your solution? Consider the case where the number of substitutions is very large. How would you handle edge cases like overlapping substitutions or empty input strings?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
We're given a main string and a list of substitution rules. The brute force method is all about trying every possible combination of applying those substitutions in the correct places in the main string.
Here's how the algorithm would work step-by-step:
def apply_substitutions_brute_force(main_string, substitution_rules):
possible_strings = {main_string}
# We continue until no new strings are generated
while True:
new_strings = set()
for current_string in possible_strings:
for starting_position in range(len(current_string) + 1):
for old_substring, new_substring in substitution_rules:
# Check if the rule applies at the current position
if current_string[starting_position:].startswith(old_substring):
# Apply the substitution
substituted_string = current_string[:starting_position] + \
new_substring + \
current_string[starting_position + len(old_substring):]
new_strings.add(substituted_string)
# If no new strings were generated, we're done
if not new_strings:
break
# Update the set of possible strings
possible_strings.update(new_strings)
# Return the lexicographically smallest string
return min(possible_strings) if possible_strings else ""
We want to efficiently replace parts of a string with given substitutions. The key idea is to apply the substitutions in a single pass through the string, ensuring no substitution interferes with another. To achieve this, we sort the substitutions by their starting positions in the string.
Here's how the algorithm would work step-by-step:
def apply_substitutions(original_string, substitutions):
sorted_substitutions = sorted(substitutions, key=lambda x: x[0])
modified_string = list(original_string)
applied_substitutions = []
for start_index, source, target in sorted_substitutions:
overlap = False
# Check for overlaps with previously applied substitutions
for applied_start, applied_end in applied_substitutions:
if max(start_index, applied_start) < min(start_index + len(source), applied_end):
overlap = True
break
# Skip substitutions that overlap with previously applied ones
if overlap:
continue
original_substring = "".join(modified_string[start_index:start_index + len(source)])
if original_substring == source:
# Avoid string slicing by using lists
modified_string[start_index:start_index + len(source)] = list(target)
applied_substitutions.append((start_index, start_index + len(target)))
return "".join(modified_string)
Case | How to Handle |
---|---|
Null or empty input string | Return the original string immediately if the input is null or empty. |
Null or empty substitutions list | Return the original string immediately if the substitutions list is null or empty. |
Substitutions list contains null keys or values | Ignore substitutions with null keys or values to prevent NullPointerExceptions. |
Substitutions overlap each other in the string | Iterate through the string and apply substitutions in order of appearance, handling potential conflicts appropriately. |
Substitution value is an empty string | Treat the substitution as a deletion of the key from the input string. |
Extremely long input string or large number of substitutions leading to potential performance issues | Use StringBuilder for efficient string concatenation and consider optimizing the search algorithm for large inputs. |
Substitution key is a substring of another substitution key | Sort the substitutions by the length of the key in descending order to prioritize longer matches. |
Substitution results in an infinite loop (e.g., substituting 'a' with 'b' and 'b' with 'a') | Introduce a limit on the number of substitutions to prevent infinite loops and log a warning if the limit is reached. |