Facebook Pixel

2068. Check Whether Two Strings are Almost Equivalent

EasyHash TableStringCounting
Leetcode Link

Problem Description

You are given two strings word1 and word2, both of the same length n. Your task is to determine if these two strings are "almost equivalent."

Two strings are considered almost equivalent if, for every letter from 'a' to 'z', the absolute difference between how many times that letter appears in word1 versus word2 is at most 3.

For example:

  • If the letter 'a' appears 5 times in word1 and 2 times in word2, the difference is |5 - 2| = 3, which is acceptable.
  • If the letter 'b' appears 7 times in word1 and 3 times in word2, the difference is |7 - 3| = 4, which exceeds 3, so the strings would not be almost equivalent.

The function should return true if the strings are almost equivalent, and false otherwise.

The solution uses a Counter to track the frequency differences. It first counts all characters in word1, then subtracts the counts for characters in word2. This gives us the difference in frequencies for each character. Finally, it checks if all absolute differences are at most 3.

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 track the frequency difference for each letter between the two strings. Instead of counting frequencies separately for both strings and then comparing them, we can use a single counter to directly compute the differences.

Think of it like a balance sheet: we add counts for characters from word1 (positive contributions) and subtract counts for characters from word2 (negative contributions). After processing both strings, each value in our counter represents the net difference in frequency for that character.

For instance, if 'a' appears 5 times in word1 and 2 times in word2, our counter would show cnt['a'] = 5 - 2 = 3. This direct difference calculation is more efficient than maintaining two separate frequency maps.

The beauty of this approach is that after processing both strings, we simply need to check if any character has an absolute frequency difference greater than 3. If all differences are within the range [-3, 3], the strings are almost equivalent.

Using Python's Counter class makes this particularly elegant - we can initialize it with word1, then decrement counts for each character in word2 using subtraction. The final check all(abs(x) <= 3 for x in cnt.values()) ensures every frequency difference satisfies our constraint.

Solution Approach

The solution uses a counting approach with a single pass through both strings to determine if they are almost equivalent.

Step 1: Initialize the Counter We create a Counter object initialized with word1. This automatically counts the frequency of each character in the first string. For example, if word1 = "aaab", the counter would be {'a': 3, 'b': 1}.

Step 2: Process the Second String We iterate through each character c in word2 and decrement its count in our counter:

for c in word2:
    cnt[c] -= 1

This subtraction effectively computes the frequency difference. If a character appears in word2 but not in word1, the counter will have a negative value for that character.

Step 3: Check the Differences After processing both strings, each value in cnt represents the frequency difference for that character between word1 and word2. We use:

all(abs(x) <= 3 for x in cnt.values())

This checks if every frequency difference has an absolute value of at most 3.

Example Walkthrough:

  • word1 = "aaaa", word2 = "bbb"
  • After initializing: cnt = {'a': 4}
  • After processing word2: cnt = {'a': 4, 'b': -3}
  • Check: |4| = 4 > 3, so return false

The time complexity is O(n) where n is the length of the strings, and space complexity is O(1) since we only store at most 26 letters in the counter.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with word1 = "abcde" and word2 = "aabcd".

Step 1: Initialize Counter with word1

  • Process each character in word1 = "abcde"
  • Counter becomes: {'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1}

Step 2: Subtract counts for word2

  • Process each character in word2 = "aabcd":
    • 'a': cnt['a'] = 1 - 1 = 0
    • 'a': cnt['a'] = 0 - 1 = -1
    • 'b': cnt['b'] = 1 - 1 = 0
    • 'c': cnt['c'] = 1 - 1 = 0
    • 'd': cnt['d'] = 1 - 1 = 0
  • Counter becomes: {'a': -1, 'b': 0, 'c': 0, 'd': 0, 'e': 1}

Step 3: Check all differences

  • Check each value in the counter:
    • |−1| = 1 ≤ 3 ✓
    • |0| = 0 ≤ 3 ✓
    • |0| = 0 ≤ 3 ✓
    • |0| = 0 ≤ 3 ✓
    • |1| = 1 ≤ 3 ✓
  • All differences are at most 3, so return true

The key insight: the counter directly tracks the net difference for each character. Positive values mean the character appears more in word1, negative values mean it appears more in word2, and the absolute value gives us the actual difference we need to check.

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def checkAlmostEquivalent(self, word1: str, word2: str) -> bool:
5        # Count frequency of each character in word1
6        char_count = Counter(word1)
7      
8        # Subtract frequency of each character in word2
9        # This gives us the difference in frequencies between word1 and word2
10        for char in word2:
11            char_count[char] -= 1
12      
13        # Check if all frequency differences are at most 3 in absolute value
14        # Two strings are almost equivalent if for every letter,
15        # the absolute difference in frequencies is at most 3
16        return all(abs(freq_diff) <= 3 for freq_diff in char_count.values())
17
1class Solution {
2    /**
3     * Checks if two strings are almost equivalent.
4     * Two strings are almost equivalent if the frequency difference of each character
5     * between the two strings is at most 3.
6     * 
7     * @param word1 the first string to compare
8     * @param word2 the second string to compare
9     * @return true if the strings are almost equivalent, false otherwise
10     */
11    public boolean checkAlmostEquivalent(String word1, String word2) {
12        // Array to store the frequency difference for each lowercase letter (a-z)
13        int[] frequencyDifference = new int[26];
14      
15        // Increment count for each character in word1
16        for (int i = 0; i < word1.length(); i++) {
17            char currentChar = word1.charAt(i);
18            int charIndex = currentChar - 'a';
19            frequencyDifference[charIndex]++;
20        }
21      
22        // Decrement count for each character in word2
23        for (int i = 0; i < word2.length(); i++) {
24            char currentChar = word2.charAt(i);
25            int charIndex = currentChar - 'a';
26            frequencyDifference[charIndex]--;
27        }
28      
29        // Check if any character has a frequency difference greater than 3
30        for (int difference : frequencyDifference) {
31            if (Math.abs(difference) > 3) {
32                return false;
33            }
34        }
35      
36        // All characters have frequency difference at most 3
37        return true;
38    }
39}
40
1class Solution {
2public:
3    bool checkAlmostEquivalent(string word1, string word2) {
4        // Initialize frequency array for 26 lowercase letters
5        int frequency[26] = {0};
6      
7        // Increment count for each character in word1
8        for (char& ch : word1) {
9            frequency[ch - 'a']++;
10        }
11      
12        // Decrement count for each character in word2
13        for (char& ch : word2) {
14            frequency[ch - 'a']--;
15        }
16      
17        // Check if the absolute difference for any character exceeds 3
18        for (int i = 0; i < 26; i++) {
19            if (abs(frequency[i]) > 3) {
20                return false;  // Words are not almost equivalent
21            }
22        }
23      
24        return true;  // Words are almost equivalent
25    }
26};
27
1/**
2 * Checks if two words are almost equivalent.
3 * Two words are almost equivalent if the frequency difference of each character
4 * between the two words is at most 3.
5 * 
6 * @param word1 - The first word to compare
7 * @param word2 - The second word to compare
8 * @returns true if the words are almost equivalent, false otherwise
9 */
10function checkAlmostEquivalent(word1: string, word2: string): boolean {
11    // Initialize frequency counter array for 26 lowercase letters
12    // Positive values indicate excess in word1, negative values indicate excess in word2
13    const frequencyDifference: number[] = new Array(26).fill(0);
14  
15    // Count character frequencies from word1 (increment counter)
16    for (const character of word1) {
17        // Convert character to index (0-25) by subtracting ASCII value of 'a' (97)
18        const index: number = character.charCodeAt(0) - 97;
19        frequencyDifference[index]++;
20    }
21  
22    // Subtract character frequencies from word2 (decrement counter)
23    for (const character of word2) {
24        // Convert character to index (0-25) by subtracting ASCII value of 'a' (97)
25        const index: number = character.charCodeAt(0) - 97;
26        frequencyDifference[index]--;
27    }
28  
29    // Check if all frequency differences are within the allowed range (-3 to 3)
30    return frequencyDifference.every((difference: number) => Math.abs(difference) <= 3);
31}
32

Time and Space Complexity

The time complexity is O(n + m) where n is the length of word1 and m is the length of word2. The Counter(word1) operation takes O(n) time to count all characters in word1. The loop iterating through word2 takes O(m) time. Finally, checking all values in the counter takes O(C) time where C is the size of the character set (at most 26 for lowercase English letters). Since C is constant and typically n and m are of similar magnitude, this can be simplified to O(n) when considering both strings have comparable lengths.

The space complexity is O(C) where C is the size of the character set. The Counter object stores at most C = 26 key-value pairs (one for each lowercase English letter that appears in either string). This is constant space with respect to the input size, as the number of distinct characters is bounded by the alphabet size regardless of the string lengths.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Forgetting to Handle Characters Present in Only One String

A common mistake is assuming that both strings contain the same set of characters. The Counter approach handles this elegantly, but if implementing manually with a dictionary, you might forget to account for characters that appear in word2 but not in word1.

Incorrect approach:

def checkAlmostEquivalent(self, word1: str, word2: str) -> bool:
    freq1 = {}
    for c in word1:
        freq1[c] = freq1.get(c, 0) + 1
  
    # Wrong: This only checks characters that exist in word1
    for c in freq1:
        count2 = word2.count(c)
        if abs(freq1[c] - count2) > 3:
            return False
    return True

This fails when word2 has characters not in word1. For example, with word1 = "aaa" and word2 = "bbbb", it would incorrectly return True because it never checks the frequency of 'b'.

Solution: Always check all 26 lowercase letters or use the Counter subtraction approach which automatically handles missing keys.

Pitfall 2: Using Two Separate Counters Without Proper Comparison

Another mistake is creating two separate counters and not properly comparing all characters from both.

Incorrect approach:

def checkAlmostEquivalent(self, word1: str, word2: str) -> bool:
    cnt1 = Counter(word1)
    cnt2 = Counter(word2)
  
    # Wrong: Only checks keys in cnt1
    for char in cnt1:
        if abs(cnt1[char] - cnt2.get(char, 0)) > 3:
            return False
    return True

Solution: Ensure you check all unique characters from both strings:

def checkAlmostEquivalent(self, word1: str, word2: str) -> bool:
    cnt1 = Counter(word1)
    cnt2 = Counter(word2)
  
    # Check all characters from both counters
    all_chars = set(cnt1.keys()) | set(cnt2.keys())
    for char in all_chars:
        if abs(cnt1.get(char, 0) - cnt2.get(char, 0)) > 3:
            return False
    return True

The original solution avoids this pitfall by using Counter subtraction, which automatically handles all characters from both strings.

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

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings

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

Load More