Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. For each query word, we check it against all dictionary words
  2. Count the character differences using sum(a != b for a, b in zip(s, t))
  3. If we find any dictionary word with less than 3 differences, the query word qualifies
  4. 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:

  1. Initialize Result List: Create an empty list ans to store the words that satisfy our condition.

  2. Outer Loop - Iterate Through Queries: For each word s in the queries array:

  3. Inner Loop - Check Against Dictionary: For each word t in the dictionary array:

    • Calculate Edit Distance: Use sum(a != b for a, b in zip(s, t)) to count the number of positions where characters differ
      • zip(s, t) pairs up characters at the same positions from both words
      • a != b returns True (counted as 1) when characters differ, False (counted as 0) when they match
      • sum() adds up all the differences to get the total edit distance
  4. 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)
  5. 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

  6. 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 Evaluator

Example 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 takes O(l) time where l 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 all m 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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the maximum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More