2452. Words Within Two Edits of Dictionary
Problem Description
You have two arrays of strings: queries
and dictionary
. Every word in both arrays consists only of lowercase English letters and all words have the same length.
An edit operation allows you to take a word from queries
and change any single letter in it to any other letter.
Your task is to find all words from queries
that can be transformed into at least one word from dictionary
using at most two edits.
For example, if you have the word "cat" and you want to transform it to "dog":
- Change 'c' to 'd' (1 edit: "dat")
- Change 'a' to 'o' (2 edits: "dot")
- Change 't' to 'g' (3 edits: "dog")
This would require 3 edits total, which exceeds our limit of 2.
The function should return a list containing all words from queries
that match with at least one word from dictionary
after making at most 2 edits. The words in the result should maintain the same order as they appear in the original queries
array.
The solution works by checking each word in queries
against every word in dictionary
. For each pair, it counts the number of positions where the characters differ. If this count is less than 3 (meaning 0, 1, or 2 edits are needed), the word from queries
is added to the result and we move on to check the next query word.
Intuition
The key insight is that we need to measure how "different" two words are from each other. Since all words have the same length, we can compare them character by character at each position.
When comparing two words of equal length, the number of edits needed to transform one into the other is simply the count of positions where they have different characters. For instance, comparing "cat" and "car":
- Position 0: 'c' = 'c' (match)
- Position 1: 'a' = 'a' (match)
- Position 2: 't' β 'r' (different)
This gives us 1 difference, meaning we need exactly 1 edit to transform "cat" to "car".
Since we want words that can be transformed with at most 2 edits, we're looking for word pairs where the number of differing positions is 0, 1, or 2. In other words, we need difference_count < 3
.
The brute force approach naturally follows from this observation:
- For each query word, we check it against all dictionary words
- Count the character differences using
sum(a != b for a, b in zip(s, t))
- If we find any dictionary word with less than 3 differences, the query word qualifies
- Once we find a match, we can stop checking that query word against remaining dictionary words (hence the
break
)
This approach works well when the word lengths and array sizes are reasonable, as we're essentially doing a simple character-by-character comparison for each word pair.
Learn more about Trie patterns.
Solution Approach
The solution uses a brute force enumeration approach with nested loops to check every word from queries
against every word in dictionary
.
Algorithm Steps:
-
Initialize Result List: Create an empty list
ans
to store the words that satisfy our condition. -
Outer Loop - Iterate Through Queries: For each word
s
in thequeries
array: -
Inner Loop - Check Against Dictionary: For each word
t
in thedictionary
array:- Calculate Edit Distance: Use
sum(a != b for a, b in zip(s, t))
to count the number of positions where characters differzip(s, t)
pairs up characters at the same positions from both wordsa != b
returnsTrue
(counted as 1) when characters differ,False
(counted as 0) when they matchsum()
adds up all the differences to get the total edit distance
- Calculate Edit Distance: Use
-
Check Edit Condition: If the edit distance is less than 3 (meaning 0, 1, or 2 edits needed):
- Add the current query word
s
to the result list - Use
break
to exit the inner loop immediately (we only need to find one matching dictionary word)
- Add the current query word
-
Continue or Move On: If no dictionary word matches (edit distance < 3), the inner loop completes naturally and we move to the next query word
-
Return Result: After checking all query words, return the
ans
list containing all qualifying words in their original order
Time Complexity: O(n * m * k)
where n
is the length of queries, m
is the length of dictionary, and k
is the length of each word.
Space Complexity: O(1)
extra space (not counting the output array), as we only use a few variables for iteration and comparison.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works.
Given:
queries = ["word", "note", "ants", "wood"]
dictionary = ["wood", "joke", "moat"]
Step-by-step execution:
Query 1: "word"
- Compare with "wood":
- w=w β, o=o β, rβ o β, d=d β β 1 difference
- Since 1 < 3, add "word" to result and break
- Result:
["word"]
Query 2: "note"
- Compare with "wood":
- nβ w β, o=o β, tβ o β, eβ d β β 3 differences
- Since 3 is not < 3, continue
- Compare with "joke":
- nβ j β, o=o β, tβ k β, e=e β β 2 differences
- Since 2 < 3, add "note" to result and break
- Result:
["word", "note"]
Query 3: "ants"
- Compare with "wood":
- aβ w β, nβ o β, tβ o β, sβ d β β 4 differences
- Since 4 is not < 3, continue
- Compare with "joke":
- aβ j β, nβ o β, tβ k β, sβ e β β 4 differences
- Since 4 is not < 3, continue
- Compare with "moat":
- aβ m β, nβ o β, tβ a β, sβ t β β 4 differences
- Since 4 is not < 3, continue
- No matches found, "ants" is not added
Query 4: "wood"
- Compare with "wood":
- w=w β, o=o β, o=o β, d=d β β 0 differences
- Since 0 < 3, add "wood" to result and break
- Result:
["word", "note", "wood"]
Final Output: ["word", "note", "wood"]
The algorithm successfully identifies that "word" needs 1 edit to become "wood", "note" needs 2 edits to become "joke", and "wood" needs 0 edits to match "wood" in the dictionary. The word "ants" requires more than 2 edits to match any dictionary word, so it's excluded from the result.
Solution Implementation
1from typing import List
2
3class Solution:
4 def twoEditWords(self, queries: List[str], dictionary: List[str]) -> List[str]:
5 """
6 Find all words from queries that can be matched with at least one word
7 in dictionary with at most 2 character differences.
8
9 Args:
10 queries: List of query strings to check
11 dictionary: List of reference strings to compare against
12
13 Returns:
14 List of query strings that have at least one dictionary match
15 with 2 or fewer character differences
16 """
17 result = []
18
19 # Check each query word
20 for query_word in queries:
21 # Try to find a matching word in the dictionary
22 for dict_word in dictionary:
23 # Count the number of differing characters at the same positions
24 differences = sum(char_q != char_d
25 for char_q, char_d in zip(query_word, dict_word))
26
27 # If differences are at most 2, add query word to result
28 if differences <= 2:
29 result.append(query_word)
30 break # Found a match, no need to check other dictionary words
31
32 return result
33
1class Solution {
2 /**
3 * Finds all query strings that can be transformed into at least one dictionary word
4 * with at most 2 character edits (substitutions).
5 *
6 * @param queries Array of query strings to check
7 * @param dictionary Array of dictionary words to compare against
8 * @return List of query strings that match the criteria
9 */
10 public List<String> twoEditWords(String[] queries, String[] dictionary) {
11 // Result list to store valid query strings
12 List<String> result = new ArrayList<>();
13
14 // Get the length of strings (assuming all strings have the same length)
15 int stringLength = queries[0].length();
16
17 // Iterate through each query string
18 for (String queryString : queries) {
19 // Check the current query against each dictionary word
20 for (String dictionaryWord : dictionary) {
21 // Count the number of differing characters
22 int differenceCount = 0;
23
24 // Compare each character position
25 for (int i = 0; i < stringLength; ++i) {
26 if (queryString.charAt(i) != dictionaryWord.charAt(i)) {
27 differenceCount++;
28 }
29 }
30
31 // If the difference is at most 2, add to result and move to next query
32 if (differenceCount < 3) {
33 result.add(queryString);
34 break; // Found a match, no need to check other dictionary words
35 }
36 }
37 }
38
39 return result;
40 }
41}
42
1class Solution {
2public:
3 vector<string> twoEditWords(vector<string>& queries, vector<string>& dictionary) {
4 vector<string> result;
5
6 // Iterate through each query word
7 for (auto& queryWord : queries) {
8 // Check if current query word matches any dictionary word with at most 2 differences
9 for (auto& dictWord : dictionary) {
10 int differenceCount = 0;
11
12 // Count character differences between query word and dictionary word
13 for (int i = 0; i < queryWord.size(); ++i) {
14 if (queryWord[i] != dictWord[i]) {
15 differenceCount++;
16 }
17 }
18
19 // If at most 2 characters differ, add query word to result and move to next query
20 if (differenceCount <= 2) {
21 result.emplace_back(queryWord);
22 break; // Found a match, no need to check other dictionary words
23 }
24 }
25 }
26
27 return result;
28 }
29};
30
1/**
2 * Finds all queries that differ from at least one dictionary word by at most 2 characters
3 * @param queries - Array of query strings to check
4 * @param dictionary - Array of dictionary strings to compare against
5 * @returns Array of queries that have at most 2 character differences with at least one dictionary word
6 */
7function twoEditWords(queries: string[], dictionary: string[]): string[] {
8 // Get the length of the first query string (assuming all strings have the same length)
9 const stringLength = queries[0].length;
10
11 // Filter queries based on whether they have at most 2 differences with any dictionary word
12 return queries.filter(queryString => {
13 // Check the current query against each word in the dictionary
14 for (const dictionaryWord of dictionary) {
15 let differenceCount = 0;
16
17 // Count character differences between query and dictionary word
18 for (let charIndex = 0; charIndex < stringLength; ++charIndex) {
19 if (queryString[charIndex] !== dictionaryWord[charIndex]) {
20 ++differenceCount;
21 }
22 }
23
24 // If difference is at most 2, include this query in the result
25 if (differenceCount < 3) {
26 return true;
27 }
28 }
29
30 // No dictionary word found with at most 2 differences
31 return false;
32 });
33}
34
Time and Space Complexity
Time Complexity: O(m Γ n Γ l)
The algorithm uses nested loops:
- The outer loop iterates through all
m
queries - The inner loop iterates through all
n
words in the dictionary - For each pair of words, the
zip
operation and character comparison takesO(l)
time wherel
is the length of the words
In the worst case, we compare every query with every dictionary word, and for each comparison, we check all l
characters. Therefore, the total time complexity is O(m Γ n Γ l)
.
Space Complexity: O(m)
The space complexity analysis:
- The
ans
list stores the matching queries, which in the worst case could contain allm
queries if they all match at least one dictionary word - The
zip
operation creates an iterator that doesn't consume additional space proportional to the input - The generator expression
sum(a != b for a, b in zip(s, t))
computes the sum on-the-fly without creating an intermediate list
Therefore, the space complexity is O(m)
for storing the result list.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Assuming Words Have Different Lengths
One common mistake is trying to handle words of different lengths when the problem explicitly states all words have the same length. Adding unnecessary length checks can complicate the code and potentially introduce bugs.
Incorrect approach:
# Unnecessary length check
if len(query_word) != len(dict_word):
continue
differences = sum(char_q != char_d
for char_q, char_d in zip(query_word, dict_word))
Solution: Trust the problem constraints and keep the code simple. The given solution correctly assumes equal lengths.
2. Adding Duplicate Words to Result
A critical pitfall is forgetting to break after finding a match, which could lead to adding the same query word multiple times if it matches multiple dictionary words.
Incorrect approach:
for query_word in queries:
for dict_word in dictionary:
differences = sum(char_q != char_d
for char_q, char_d in zip(query_word, dict_word))
if differences <= 2:
result.append(query_word)
# Missing break - word gets added multiple times!
Solution: Always include break
after adding a query word to ensure each query appears at most once in the result.
3. Using Set for Result Collection
Using a set to avoid duplicates seems logical but destroys the original order of queries, which the problem requires to be maintained.
Incorrect approach:
result = set() # Wrong! Loses order
for query_word in queries:
for dict_word in dictionary:
if sum(char_q != char_d for char_q, char_d in zip(query_word, dict_word)) <= 2:
result.add(query_word)
break
return list(result) # Order is not preserved
Solution: Use a list and rely on the break
statement to prevent duplicates while maintaining order.
4. Misunderstanding Edit Distance
Confusing character differences with actual edit operations like insertions or deletions. The problem only considers character substitutions at fixed positions.
Incorrect interpretation:
# Wrong: Trying to implement Levenshtein distance with insertions/deletions
def edit_distance(s1, s2):
# Complex dynamic programming for general edit distance
# This is NOT what the problem asks for
Solution: Simply count position-wise character differences using zip()
and comparison, as shown in the correct solution.
In a binary min heap, the maximum element can be found in:
Recommended Readings
Trie Introduction Imagine you have a magic dictionary where as soon as you start spelling a word it immediately tells you how many words that start with those letters How cool would that be This is exactly the kind of magic a data structure called trie performs What's a Trie
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!