833. Find And Replace in String
Problem Description
You are given a string s
and need to perform k
replacement operations on it. The replacements are defined by three arrays of equal length k
:
indices
: the starting positions where replacements should be attemptedsources
: the substrings to look for at those positionstargets
: what to replace the source substrings with
For each replacement operation at index i
:
- Check if the substring
sources[i]
appears in the original strings
starting at positionindices[i]
- If it matches, replace that substring with
targets[i]
- If it doesn't match, do nothing
All replacements happen simultaneously on the original string. This means:
- Replacements are independent of each other
- The position indices always refer to the original string, not any intermediate result
- The problem guarantees that replacements won't overlap
For example, with s = "abcd"
, if we have:
indices[i] = 0
sources[i] = "ab"
targets[i] = "eee"
Since "ab"
appears at index 0 in s
, we replace it with "eee"
, resulting in "eeecd"
.
The task is to return the final string after applying all valid replacement operations.
Intuition
Since all replacements must happen simultaneously on the original string, we need to first identify which positions in the original string should be replaced before actually building the result. This prevents one replacement from affecting another.
The key insight is to create a mapping that tells us: "at position i
in the original string, should we perform a replacement, and if so, which one?" We can use an array d
of size n
(length of string) where d[i]
stores the index of the replacement operation to perform at position i
, or -1
if no replacement should happen there.
First pass: For each replacement operation k
, we check if sources[k]
matches the substring starting at indices[k]
. If it matches, we mark d[indices[k]] = k
, indicating that at this position we should apply the k
-th replacement.
Second pass: We traverse the original string from left to right. At each position i
:
- If
d[i]
indicates a replacement (not-1
), we appendtargets[d[i]]
to our result and jump forward by the length ofsources[d[i]]
(since we've just replaced that entire substring) - If no replacement is needed, we simply copy the current character and move to the next position
This two-pass approach ensures that all replacements are based on the original string positions and don't interfere with each other, while efficiently building the final result in a single traversal.
Learn more about Sorting patterns.
Solution Approach
The solution follows a two-phase approach: marking positions for replacement, then building the result string.
Phase 1: Mark Replacement Positions
We create an array d
of size n
(length of string s
) initialized with -1
values. This array serves as a lookup table where d[i]
tells us which replacement operation (if any) should be performed at position i
.
d = [-1] * n
for k, (i, src) in enumerate(zip(indices, sources)):
if s.startswith(src, i):
d[i] = k
For each replacement operation indexed by k
:
- We check if the substring
sources[k]
matches at positionindices[k]
usings.startswith(src, i)
- If it matches, we store
d[indices[k]] = k
, marking that thek
-th replacement should occur at this position - The
~
operator (bitwise NOT) is later used to check ifd[i]
is not-1
, since~(-1) = 0
which evaluates toFalse
Phase 2: Build the Result String
We traverse the original string and construct the result based on our marking array:
ans = []
i = 0
while i < n:
if ~d[i]: # if d[i] != -1
ans.append(targets[d[i]])
i += len(sources[d[i]])
else:
ans.append(s[i])
i += 1
At each position i
:
- If
d[i]
contains a valid replacement index (not-1
), we:- Append the corresponding
targets[d[i]]
to our result - Skip ahead by
len(sources[d[i]])
positions, as we've just replaced that entire substring
- Append the corresponding
- Otherwise, we copy the current character and move to the next position
Finally, we join all parts to form the resulting string: return "".join(ans)
This approach ensures O(n) time complexity for building the result string, with the overall complexity being O(n + kΓm) where k
is the number of operations and m
is the average length of source strings.
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 walk through a concrete example to illustrate the solution approach.
Given:
s = "abcd"
indices = [0, 2]
sources = ["a", "cd"]
targets = ["eee", "ffff"]
Phase 1: Mark Replacement Positions
First, we create an array d
of length 4 (same as string length) initialized with -1
:
d = [-1, -1, -1, -1]
Now we check each replacement operation:
Operation 0 (k=0):
- Check if
sources[0] = "a"
appears atindices[0] = 0
in string "abcd" - Yes! "abcd" starts with "a" at position 0
- Mark
d[0] = 0
(indicating operation 0 should happen at position 0) d = [0, -1, -1, -1]
Operation 1 (k=1):
- Check if
sources[1] = "cd"
appears atindices[1] = 2
in string "abcd" - Yes! Starting at position 2, we have "cd"
- Mark
d[2] = 1
(indicating operation 1 should happen at position 2) d = [0, -1, 1, -1]
Phase 2: Build the Result String
Now we traverse the original string using our marking array:
ans = [] i = 0
Position i=0:
- Check
d[0] = 0
(not -1, so a replacement exists) - Append
targets[0] = "eee"
to result - Jump forward by
len(sources[0]) = len("a") = 1
ans = ["eee"]
,i = 1
Position i=1:
- Check
d[1] = -1
(no replacement) - Append
s[1] = "b"
to result - Move to next position
ans = ["eee", "b"]
,i = 2
Position i=2:
- Check
d[2] = 1
(not -1, so a replacement exists) - Append
targets[1] = "ffff"
to result - Jump forward by
len(sources[1]) = len("cd") = 2
ans = ["eee", "b", "ffff"]
,i = 4
Position i=4:
- We've reached the end (i >= 4)
Final Result:
Join all parts: "".join(["eee", "b", "ffff"]) = "eeebffff"
The key insight is that by marking positions first, we ensure all replacements reference the original string positions. When we find "cd" at position 2, it's based on the original "abcd", not any intermediate result after replacing "a" with "eee".
Solution Implementation
1class Solution:
2 def findReplaceString(
3 self, s: str, indices: List[int], sources: List[str], targets: List[str]
4 ) -> str:
5 """
6 Performs multiple substring replacements on string s based on given indices, sources, and targets.
7 Only replaces if the source string matches at the specified index.
8
9 Args:
10 s: The original string to perform replacements on
11 indices: List of starting positions for potential replacements
12 sources: List of source strings to look for at corresponding indices
13 targets: List of target strings to replace sources with
14
15 Returns:
16 The modified string after all valid replacements
17 """
18 string_length = len(s)
19
20 # Create a mapping array where replacement_map[i] stores the index of replacement operation
21 # to perform at position i in the string, or -1 if no replacement
22 replacement_map = [-1] * string_length
23
24 # Build the replacement map by checking each source-index pair
25 for operation_index, (start_index, source_string) in enumerate(zip(indices, sources)):
26 # Check if the source string matches at the specified index
27 if s.startswith(source_string, start_index):
28 replacement_map[start_index] = operation_index
29
30 # Build the result string by traversing the original string
31 result = []
32 current_position = 0
33
34 while current_position < string_length:
35 # Check if there's a replacement operation at current position
36 # Note: ~(-1) equals 0 (False), while ~(non-negative) is non-zero (True)
37 if ~replacement_map[current_position]:
38 # Perform replacement: append the target string
39 operation_index = replacement_map[current_position]
40 result.append(targets[operation_index])
41 # Skip past the source string that was replaced
42 current_position += len(sources[operation_index])
43 else:
44 # No replacement: keep the original character
45 result.append(s[current_position])
46 current_position += 1
47
48 return "".join(result)
49
1class Solution {
2 public String findReplaceString(String s, int[] indices, String[] sources, String[] targets) {
3 int stringLength = s.length();
4
5 // Create an array to store which replacement operation (if any) should be performed at each index
6 // -1 means no replacement, otherwise stores the index of the operation in the input arrays
7 int[] replacementMapping = new int[stringLength];
8 Arrays.fill(replacementMapping, -1);
9
10 // Check each replacement operation to see if it's valid
11 for (int operationIndex = 0; operationIndex < indices.length; operationIndex++) {
12 int startPosition = indices[operationIndex];
13
14 // Verify if the source string matches at the specified position
15 if (s.startsWith(sources[operationIndex], startPosition)) {
16 // Mark this position with the operation index for later replacement
17 replacementMapping[startPosition] = operationIndex;
18 }
19 }
20
21 // Build the result string by iterating through the original string
22 StringBuilder result = new StringBuilder();
23
24 for (int currentIndex = 0; currentIndex < stringLength;) {
25 // Check if there's a valid replacement operation at the current position
26 if (replacementMapping[currentIndex] >= 0) {
27 // Perform the replacement by appending the target string
28 int operationIndex = replacementMapping[currentIndex];
29 result.append(targets[operationIndex]);
30
31 // Skip past the source string that was replaced
32 currentIndex += sources[operationIndex].length();
33 } else {
34 // No replacement at this position, copy the original character
35 result.append(s.charAt(currentIndex));
36 currentIndex++;
37 }
38 }
39
40 return result.toString();
41 }
42}
43
1class Solution {
2public:
3 string findReplaceString(string s, vector<int>& indices, vector<string>& sources, vector<string>& targets) {
4 int stringLength = s.size();
5
6 // Create a mapping array where mappingArray[i] stores the index of replacement operation
7 // that should be performed at position i in the original string
8 // -1 means no replacement at that position
9 vector<int> mappingArray(stringLength, -1);
10
11 // Check each replacement operation
12 for (int operationIndex = 0; operationIndex < indices.size(); ++operationIndex) {
13 int startPosition = indices[operationIndex];
14
15 // Verify if the source string matches at the specified position
16 if (s.compare(startPosition, sources[operationIndex].size(), sources[operationIndex]) == 0) {
17 // Mark this position with the operation index for later replacement
18 mappingArray[startPosition] = operationIndex;
19 }
20 }
21
22 // Build the result string
23 string result;
24 for (int currentPosition = 0; currentPosition < stringLength;) {
25 // Check if there's a valid replacement at current position
26 if (mappingArray[currentPosition] != -1) {
27 // Perform replacement: append the target string
28 int operationIndex = mappingArray[currentPosition];
29 result += targets[operationIndex];
30 // Skip the length of the source string that was replaced
31 currentPosition += sources[operationIndex].size();
32 } else {
33 // No replacement, copy the original character
34 result += s[currentPosition];
35 currentPosition++;
36 }
37 }
38
39 return result;
40 }
41};
42
1/**
2 * Replaces substrings in the original string based on given indices, sources, and targets.
3 * Each replacement is performed only if the source string matches at the specified index.
4 *
5 * @param s - The original string to perform replacements on
6 * @param indices - Array of starting indices where replacements should be attempted
7 * @param sources - Array of source strings to look for at corresponding indices
8 * @param targets - Array of target strings to replace the sources with
9 * @returns The modified string after all valid replacements
10 */
11function findReplaceString(
12 s: string,
13 indices: number[],
14 sources: string[],
15 targets: string[]
16): string {
17 const stringLength: number = s.length;
18
19 // Create a mapping array to track which replacement (if any) should occur at each position
20 // -1 means no replacement, otherwise stores the index of the replacement operation
21 const replacementMapping: number[] = Array(stringLength).fill(-1);
22
23 // Check each replacement operation and mark valid ones in the mapping
24 for (let operationIndex = 0; operationIndex < indices.length; ++operationIndex) {
25 const startIndex: number = indices[operationIndex];
26 const sourceString: string = sources[operationIndex];
27
28 // Only mark this position for replacement if the source string matches at this index
29 if (s.startsWith(sourceString, startIndex)) {
30 replacementMapping[startIndex] = operationIndex;
31 }
32 }
33
34 // Build the result string by iterating through the original string
35 const resultParts: string[] = [];
36
37 for (let currentIndex = 0; currentIndex < stringLength; ) {
38 // Check if there's a replacement operation at the current position
39 if (replacementMapping[currentIndex] >= 0) {
40 const operationIndex: number = replacementMapping[currentIndex];
41
42 // Add the target string to the result
43 resultParts.push(targets[operationIndex]);
44
45 // Skip past the source string length in the original string
46 currentIndex += sources[operationIndex].length;
47 } else {
48 // No replacement at this position, copy the original character
49 resultParts.push(s[currentIndex]);
50 currentIndex++;
51 }
52 }
53
54 // Join all parts to form the final result string
55 return resultParts.join('');
56}
57
Time and Space Complexity
Time Complexity: O(L)
where L
is the sum of the lengths of all strings (including s
, all strings in sources
, and all strings in targets
).
The analysis breaks down as follows:
- The first loop iterates through all
k
replacements, where each iteration checkss.startswith(src, i)
. This operation takesO(len(src))
time for each source string. The total time for this loop isO(sum of lengths of all source strings)
. - The second while loop traverses the string
s
once. At positioni
, if a replacement is found, we append the target string toans
(which takesO(1)
amortized time for list append) and skip ahead by the length of the source string. If no replacement is found, we append a single character. Overall, this loop processes each character ofs
exactly once, contributingO(n)
time. - The
"".join(ans)
operation at the end takesO(length of final result)
time, which is bounded byO(sum of lengths of all target strings + n)
.
Since we process the original string s
and potentially all source and target strings, the total time complexity is O(n + sum of lengths of sources + sum of lengths of targets) = O(L)
.
Space Complexity: O(n)
where n
is the length of string s
.
The space analysis:
- The array
d
usesO(n)
space to store indices. - The
ans
list stores the result, which in the worst case could be longer thann
(if replacements make the string longer), but the auxiliary space used byd
is exactlyO(n)
. - Other variables use
O(1)
space.
The dominant space usage is the d
array of size n
, giving us O(n)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Processing Replacements in Sequential Order Instead of Simultaneously
A major pitfall is attempting to process replacements one by one in the order they appear in the arrays, which would cause earlier replacements to affect the indices of later ones.
Incorrect Approach:
# WRONG: This modifies the string after each replacement
for i, src, tgt in zip(indices, sources, targets):
if s[i:i+len(src)] == src:
s = s[:i] + tgt + s[i+len(src):] # This changes indices!
Why it fails: After replacing "ab" with "eee" at index 0, the string length changes, making all subsequent indices incorrect.
Solution: Use the mapping array approach shown in the correct solution, which marks all valid replacements first based on the original string positions.
2. Not Handling Overlapping Replacement Attempts
While the problem guarantees no overlaps in valid replacements, you might incorrectly assume all attempted replacements will be valid and accidentally process overlapping regions.
Incorrect Approach:
# WRONG: Assuming all replacements are valid without checking
for k in range(len(indices)):
result[indices[k]:indices[k]+len(sources[k])] = targets[k]
Solution: Always verify that sources[i]
actually matches at indices[i]
before marking it for replacement. The correct code uses s.startswith(source_string, start_index)
to validate.
3. Incorrect Index Advancement After Replacement
When building the result string, failing to skip the correct number of characters after a replacement is a common error.
Incorrect Approach:
# WRONG: Always advancing by 1 while i < n: if ~replacement_map[i]: result.append(targets[replacement_map[i]]) i += 1 # Should skip len(sources[...]) characters! else: result.append(s[i]) i += 1
Why it fails: This would include characters from the original source string that should have been replaced.
Solution: After performing a replacement, advance the position by len(sources[operation_index])
to skip over the entire replaced substring.
4. Misunderstanding the Bitwise NOT Operator
Using if replacement_map[i]:
instead of if ~replacement_map[i]:
would fail because 0
is a valid replacement index.
Incorrect Approach:
# WRONG: This treats index 0 as False if replacement_map[i]: # Fails when replacement_map[i] == 0 # perform replacement
Solution: Use ~replacement_map[i]
which only evaluates to False when the value is -1, or alternatively use explicit comparison: if replacement_map[i] != -1:
A heap is a ...?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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
Want a Structured Path to Master System Design Too? Donβt Miss This!