You are given two string arrays, queries and dictionary. All words in each array comprise of lowercase English letters and have the same length.
In one edit you can take a word from queries, and change any letter in it to any other letter. Find all words from queries that, after a maximum of two edits, equal some word from dictionary.
Return a list of all words from queries, that match with some word from dictionary after a maximum of two edits. Return the words in the same order they appear in queries.
Example 1:
Input: queries = ["word","note","ants","wood"], dictionary = ["wood","joke","moat"] Output: ["word","note","wood"] Explanation: - Changing the 'r' in "word" to 'o' allows it to equal the dictionary word "wood". - Changing the 'n' to 'j' and the 't' to 'k' in "note" changes it to "joke". - It would take more than 2 edits for "ants" to equal a dictionary word. - "wood" can remain unchanged (0 edits) and match the corresponding dictionary word. Thus, we return ["word","note","wood"].
Example 2:
Input: queries = ["yes"], dictionary = ["not"] Output: [] Explanation: Applying any two edits to "yes" cannot make it equal to "not". Thus, we return an empty array.
Constraints:
1 <= queries.length, dictionary.length <= 100n == queries[i].length == dictionary[j].length1 <= n <= 100queries[i] and dictionary[j] are composed of lowercase English letters.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 need to find words from a list that are very similar to words in a dictionary. The brute force method checks every word in our list against every word in the dictionary to see how similar they are.
Here's how the algorithm would work step-by-step:
def words_within_two_edits_of_dictionary(query_words, dictionary_words):
similar_words = []
for query_word in query_words:
is_similar = False
for dictionary_word in dictionary_words:
# Only compare words of the same length
if len(query_word) == len(dictionary_word):
difference_count = 0
for index in range(len(query_word)):
if query_word[index] != dictionary_word[index]:
difference_count += 1
# Check if edit distance is within the limit
if difference_count <= 2:
is_similar = True
# No need to check other dictionary words if match found
break
# Add word to result if similar
if is_similar:
similar_words.append(query_word)
return similar_wordsTo efficiently find dictionary words within two edits of each query word, we pre-process the dictionary to create a lookup structure. This allows us to quickly check how many edits are needed to transform each query word into a potential match, avoiding redundant calculations.
Here's how the algorithm would work step-by-step:
def words_within_two_edits(query_words, dictionary):
result = []
dictionary_set = set(dictionary)
for query_word in query_words:
# Generate words within two edits
close_matches = find_close_matches(query_word, dictionary_set)
result.append(close_matches)
return result
def find_close_matches(query_word, dictionary_set):
close_words = []
queue = [(query_word, 0)]
visited = {query_word}
while queue:
current_word, edit_distance = queue.pop(0)
if current_word in dictionary_set:
close_words.append(current_word)
# Only explore further if within the edit distance limit
if edit_distance < 2:
for next_word in generate_one_edit_words(current_word):
if next_word not in visited:
queue.append((next_word, edit_distance + 1))
visited.add(next_word)
return list(set(close_words))
def generate_one_edit_words(word):
alphabet = 'abcdefghijklmnopqrstuvwxyz'
one_edit_words = set()
# Deletions
for i in range(len(word)):
one_edit_words.add(word[:i] + word[i+1:])
# Insertions
for i in range(len(word) + 1):
for char in alphabet:
one_edit_words.add(word[:i] + char + word[i:])
# Substitutions
for i in range(len(word)):
for char in alphabet:
one_edit_words.add(word[:i] + char + word[i+1:])
return one_edit_words| Case | How to Handle |
|---|---|
| Null or empty dictionary | Return an empty list since no words from queries can be within two edits of any word in an empty dictionary. |
| Null or empty queries list | Return an empty list as there are no query words to check against the dictionary. |
| Empty string in dictionary or queries | Handle empty strings by considering any non-empty string to be at least one edit away; if both are empty, they are 0 edits away, so equality checks should account for this. |
| Dictionary words with significantly varying lengths compared to query words | Implement a length check before the edit distance calculation; if length difference exceeds 2, skip edit distance calculation to improve efficiency. |
| Dictionary and queries containing identical words | Ensure that identical words are considered to be within two edits of themselves by handling 0 edit differences correctly. |
| Very large dictionary and queries list impacting memory usage | Optimize memory by using iterators or generators when processing large lists to avoid loading everything into memory at once. |
| Words containing non-ASCII characters | Ensure the edit distance calculation handles Unicode or other character encodings correctly (e.g., UTF-8). |
| Large Levenshtein distance calculations leading to potential performance bottleneck | Consider implementing early stopping conditions in the Levenshtein distance calculation if the distance exceeds 2, to avoid unnecessary computations. |