You are given two strings s
and sub
. You are also given a 2D character array mappings
where mappings[i] = [oldi, newi]
indicates that you may perform the following operation any number of times:
oldi
of sub
with newi
.Each character in sub
cannot be replaced more than once.
Return true
if it is possible to make sub
a substring of s
by replacing zero or more characters according to mappings
. Otherwise, return false
.
A substring is a contiguous non-empty sequence of characters within a string.
Example 1:
Input: s = "fool3e7bar", sub = "leet", mappings = [["e","3"],["t","7"],["t","8"]] Output: true Explanation: Replace the first 'e' in sub with '3' and 't' in sub with '7'. Now sub = "l3e7" is a substring of s, so we return true.
Example 2:
Input: s = "fooleetbar", sub = "f00l", mappings = [["o","0"]] Output: false Explanation: The string "f00l" is not a substring of s and no replacements can be made. Note that we cannot replace '0' with 'o'.
Example 3:
Input: s = "Fool33tbaR", sub = "leetd", mappings = [["e","3"],["t","7"],["t","8"],["d","b"],["p","b"]] Output: true Explanation: Replace the first and second 'e' in sub with '3' and 'd' in sub with 'b'. Now sub = "l33tb" is a substring of s, so we return true.
Constraints:
1 <= sub.length <= s.length <= 5000
0 <= mappings.length <= 1000
mappings[i].length == 2
oldi != newi
s
and sub
consist of uppercase and lowercase English letters and digits.oldi
and newi
are either uppercase or lowercase English letters or digits.This problem asks whether a given substring sub
can be made into a substring of string s
after applying a series of character replacements defined in mappings
. Each character in sub
can be replaced at most once.
Both solutions presented leverage a similar strategy:
Create a Replacement Mapping: This step efficiently stores the allowed character replacements. Solution 1 uses a hash table (dictionary in Python, HashMap in Java, unordered_map in C++, map in Go), while Solution 2 uses a 2D boolean array to represent the replacement possibilities. The array approach is slightly more efficient in space and access time for this problem, as it avoids the overhead of hash table lookups and only needs to deal with ASCII characters (128 possible values).
Enumerate Substrings: The code iterates through all possible substrings of s
that have the same length as sub
.
Check for Match: For each substring, it verifies if it can be obtained from sub
by applying the replacements from the mapping. This check involves comparing each character in the substring with the corresponding character in sub
. If the characters differ, it checks if the replacement is valid based on the mapping.
Return Result: If a match is found (substring can be made from sub
), the function immediately returns true
. If no match is found after checking all substrings, it returns false
.
Both solutions have the same time complexity: O(m*n), where:
m
is the length of the string s
.n
is the length of the substring sub
.The nested loops (iterating through substrings and characters within substrings) dominate the runtime. The creation of the replacement mapping is O(k) where k is the number of mappings, which is significantly less than m*n.
Solution 1 (Hash Table): O(k + C^2), where k is the number of mappings and C is the size of the character set (number of unique characters in s and sub). The hash table's space usage is proportional to the number of unique characters in mappings
and their replacements. In the worst case, if all characters have replacement characters, it becomes O(C^2).
Solution 2 (Array): O(C^2). The 2D boolean array consumes a fixed amount of space determined by the character set size (128x128 in this case). This solution has a more predictable and consistently smaller space complexity compared to using hash tables.
Solution 1 (Hash Table based):
from collections import defaultdict
class Solution:
def matchReplacement(self, s: str, sub: str, mappings: List[List[str]]) -> bool:
# Create a replacement mapping using a defaultdict (avoids KeyError exceptions)
replacement_map = defaultdict(set)
for old, new in mappings:
replacement_map[old].add(new)
# Iterate through all substrings of s with the length of sub
for i in range(len(s) - len(sub) + 1):
substring = s[i:i + len(sub)] # Extract the substring
is_match = True
for j in range(len(sub)): # Compare the substring with sub, checking for valid replacements
if substring[j] != sub[j] and substring[j] not in replacement_map[sub[j]]:
is_match = False
break # No need to continue checking if there's an invalid replacement
if is_match:
return True # Found a match
return False # No match found
Solution 2 (Array based):
class Solution:
def matchReplacement(self, s: str, sub: str, mappings: List[List[str]]) -> bool:
# Create a 2D boolean array for replacements (more efficient for ASCII characters)
replacement_array = [[False] * 128 for _ in range(128)] # Initialize a 128x128 array
for old, new in mappings:
replacement_array[ord(old)][ord(new)] = True
# Iterate through all substrings of s with the length of sub and perform the match
for i in range(len(s) - len(sub) + 1):
substring = s[i:i + len(sub)]
is_match = True
for j in range(len(sub)):
if substring[j] != sub[j] and not replacement_array[ord(sub[j])][ord(substring[j])]:
is_match = False
break
if is_match:
return True
return False
The Java, C++, and Go versions would follow a similar structure, using the respective data structures (HashMap/unordered_map/map for Solution 1 and 2D boolean array for Solution 2). The core logic of substring enumeration and replacement checking remains consistent across all languages.