Facebook Pixel

3581. Count Odd Letters from Number 🔒

EasyHash TableStringCountingSimulation
Leetcode Link

Problem Description

You are given an integer n. Your task is to:

  1. Convert each digit of n into its corresponding lowercase English word:

    • 0 → "zero"
    • 1 → "one"
    • 2 → "two"
    • 3 → "three"
    • 4 → "four"
    • 5 → "five"
    • 6 → "six"
    • 7 → "seven"
    • 8 → "eight"
    • 9 → "nine"
  2. Concatenate these words in the same order as the digits appear in n to form a string s.

  3. Count how many distinct characters in s appear an odd number of times.

For example, if n = 123:

  • The digits are 1, 2, 3
  • Converting to words: "one", "two", "three"
  • Concatenated string s = "onetwothree"
  • Character frequencies:
    • 'o' appears 2 times (even)
    • 'n' appears 1 time (odd)
    • 'e' appears 3 times (odd)
    • 't' appears 2 times (even)
    • 'w' appears 1 time (odd)
    • 'h' appears 1 time (odd)
    • 'r' appears 1 time (odd)
  • Distinct characters with odd frequency: 'n', 'e', 'w', 'h', 'r' = 5 characters

The solution uses bit manipulation to track which characters appear an odd number of times. Each character is mapped to a bit position, and XOR operations toggle the bit whenever the character is encountered. At the end, counting the set bits gives the number of characters with odd frequencies.

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

Intuition

The key insight is recognizing that we only care about whether each character appears an odd or even number of times, not the exact count. This is a perfect scenario for using XOR operations.

Think about XOR's property: a ^ a = 0 and a ^ 0 = a. If we XOR a bit with itself an even number of times, we get 0. If we XOR it an odd number of times, we get 1. This naturally tracks parity (odd/even status).

Since we're dealing with lowercase English letters (only 26 possible characters), we can use a 32-bit integer as a bitmask where each bit position represents a letter. For example, bit 0 represents 'a', bit 1 represents 'b', and so on.

The algorithm works by:

  1. For each digit in n, we get its English word representation
  2. For each character in that word, we toggle (XOR) the corresponding bit in our mask
  3. After processing all characters, any bit that is 1 in the mask represents a character that appeared an odd number of times

Why does this work? Consider tracking the character 'e':

  • First occurrence: bit goes from 0 to 1 (odd count)
  • Second occurrence: bit goes from 1 to 0 (even count)
  • Third occurrence: bit goes from 0 to 1 (odd count)
  • And so on...

The bit position formula 1 << (ord(c) - ord('a')) converts a character to its corresponding bit position. For 'a' this gives bit 0, for 'b' bit 1, etc.

Finally, counting the number of 1s in the bitmask gives us the answer - the number of distinct characters that appear an odd number of times. This approach is elegant because it avoids using a frequency map or counter, achieving O(1) space complexity regardless of the input size.

Solution Approach

The solution uses bit manipulation to efficiently track characters with odd frequencies. Here's the step-by-step implementation:

1. Initialize the dictionary and bitmask:

d = {0: "zero", 1: "one", 2: "two", 3: "three", 4: "four", 
     5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine"}
mask = 0

The dictionary maps each digit to its English word. The mask variable is our bitmask initialized to 0, where each bit will represent whether a character appears an odd number of times.

2. Extract digits from right to left:

while n:
    x = n % 10
    n //= 10

We process the number digit by digit using modulo operation (n % 10) to get the rightmost digit, then integer division (n //= 10) to remove it.

3. Process each character in the digit's word:

for c in d[x]:
    mask ^= 1 << (ord(c) - ord("a"))

For each character c in the English word:

  • ord(c) - ord("a") calculates the character's position (0 for 'a', 1 for 'b', etc.)
  • 1 << position creates a bitmask with only that bit set
  • mask ^= ... XORs this with our running mask, toggling the bit for that character

4. Count the set bits:

return mask.bit_count()

The bit_count() method returns the number of 1s in the binary representation of mask, which equals the number of characters appearing an odd number of times.

Example walkthrough with n = 21:

  • First iteration: x = 1, word = "one"
    • 'o': toggle bit 14
    • 'n': toggle bit 13
    • 'e': toggle bit 4
  • Second iteration: x = 2, word = "two"
    • 't': toggle bit 19
    • 'w': toggle bit 22
    • 'o': toggle bit 14 (again, so it becomes 0)
  • Final mask has bits set for: 'n', 'e', 't', 'w' (4 characters with odd frequency)
  • Return 4

The time complexity is O(k) where k is the total number of characters in all digit words, and space complexity is O(1) since we only use a fixed-size integer for the bitmask.

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 n = 205:

Step 1: Initialize

  • mask = 0 (all bits are 0)
  • Dictionary maps digits to words

Step 2: Process digit 5 (rightmost)

  • x = 205 % 10 = 5
  • Word is "five"
  • Process each character:
    • 'f': bit position = 5, toggle bit 5 → mask = 100000 (binary)
    • 'i': bit position = 8, toggle bit 8 → mask = 100100000
    • 'v': bit position = 21, toggle bit 21 → mask = 1000000000100100000
    • 'e': bit position = 4, toggle bit 4 → mask = 1000000000100110000
  • Update n = 205 // 10 = 20

Step 3: Process digit 0

  • x = 20 % 10 = 0
  • Word is "zero"
  • Process each character:
    • 'z': bit position = 25, toggle bit 25 → new bit set
    • 'e': bit position = 4, toggle bit 4 → bit 4 turns OFF (was ON from "five")
    • 'r': bit position = 17, toggle bit 17 → new bit set
    • 'o': bit position = 14, toggle bit 14 → new bit set
  • Update n = 20 // 10 = 2

Step 4: Process digit 2

  • x = 2 % 10 = 2
  • Word is "two"
  • Process each character:
    • 't': bit position = 19, toggle bit 19 → new bit set
    • 'w': bit position = 22, toggle bit 22 → new bit set
    • 'o': bit position = 14, toggle bit 14 → bit 14 turns OFF (was ON from "zero")
  • n = 2 // 10 = 0, loop ends

Step 5: Count set bits

  • Final mask has bits set at positions: 5('f'), 8('i'), 21('v'), 25('z'), 17('r'), 19('t'), 22('w')
  • Characters with odd frequency: 'f', 'i', 'v', 'z', 'r', 't', 'w' = 7 characters

Note how 'e' appeared twice (even) so its bit is 0, and 'o' appeared twice (even) so its bit is 0, while all other characters appeared once (odd) so their bits are 1.

Solution Implementation

1# Dictionary mapping digits to their English word representations
2DIGIT_TO_WORD = {
3    0: "zero",
4    1: "one",
5    2: "two",
6    3: "three",
7    4: "four",
8    5: "five",
9    6: "six",
10    7: "seven",
11    8: "eight",
12    9: "nine",
13}
14
15
16class Solution:
17    def countOddLetters(self, n: int) -> int:
18        """
19        Count the number of letters that appear an odd number of times
20        when converting all digits of n to their English word representations.
21      
22        Args:
23            n: A non-negative integer
24          
25        Returns:
26            The count of letters appearing an odd number of times
27        """
28        # Bitmask to track odd/even occurrence count for each letter
29        # Bit i represents letter chr(ord('a') + i)
30        # 0 = even count, 1 = odd count
31        letter_parity_mask = 0
32      
33        # Process each digit of the number from right to left
34        while n > 0:
35            # Extract the rightmost digit
36            current_digit = n % 10
37            n //= 10
38          
39            # Get the word representation of the digit
40            word = DIGIT_TO_WORD[current_digit]
41          
42            # Toggle the bit for each character in the word
43            for char in word:
44                # Calculate bit position for this character (0-25 for 'a'-'z')
45                bit_position = ord(char) - ord("a")
46                # XOR toggles the bit: 0->1 if odd occurrences, 1->0 if even
47                letter_parity_mask ^= 1 << bit_position
48      
49        # Count and return the number of set bits (letters with odd counts)
50        return letter_parity_mask.bit_count()
51
1class Solution {
2    // Map to store digit to word mappings
3    private static final Map<Integer, String> DIGIT_TO_WORD = new HashMap<>();
4  
5    // Static initializer block to populate the digit-to-word mappings
6    static {
7        DIGIT_TO_WORD.put(0, "zero");
8        DIGIT_TO_WORD.put(1, "one");
9        DIGIT_TO_WORD.put(2, "two");
10        DIGIT_TO_WORD.put(3, "three");
11        DIGIT_TO_WORD.put(4, "four");
12        DIGIT_TO_WORD.put(5, "five");
13        DIGIT_TO_WORD.put(6, "six");
14        DIGIT_TO_WORD.put(7, "seven");
15        DIGIT_TO_WORD.put(8, "eight");
16        DIGIT_TO_WORD.put(9, "nine");
17    }
18
19    /**
20     * Counts the number of letters that appear an odd number of times
21     * when all digits of the input number are converted to their word representations.
22     * 
23     * @param n the input number to process
24     * @return the count of letters appearing an odd number of times
25     */
26    public int countOddLetters(int n) {
27        // Bitmask to track odd/even occurrence of each letter
28        // Bit i represents letter ('a' + i): 1 for odd count, 0 for even count
29        int letterOccurrenceMask = 0;
30      
31        // Process each digit of the number from right to left
32        while (n > 0) {
33            // Extract the rightmost digit
34            int currentDigit = n % 10;
35            n /= 10;
36          
37            // Get the word representation of the current digit
38            String digitWord = DIGIT_TO_WORD.get(currentDigit);
39          
40            // Toggle the bit for each letter in the word
41            for (char letter : digitWord.toCharArray()) {
42                // XOR toggles the bit: 0->1 (even->odd) or 1->0 (odd->even)
43                letterOccurrenceMask ^= 1 << (letter - 'a');
44            }
45        }
46      
47        // Count the number of set bits (letters with odd occurrences)
48        return Integer.bitCount(letterOccurrenceMask);
49    }
50}
51
1class Solution {
2public:
3    int countOddLetters(int n) {
4        // Map each digit to its English word representation
5        static const unordered_map<int, string> digitToWord = {
6            {0, "zero"},
7            {1, "one"},
8            {2, "two"},
9            {3, "three"},
10            {4, "four"},
11            {5, "five"},
12            {6, "six"},
13            {7, "seven"},
14            {8, "eight"},
15            {9, "nine"}
16        };
17
18        // Bitmask to track odd/even occurrence count of each letter
19        // Each bit position represents a letter ('a' = bit 0, 'b' = bit 1, etc.)
20        // Bit is 1 if letter appears odd times, 0 if even times
21        int letterFrequencyMask = 0;
22      
23        // Process each digit of the number
24        while (n > 0) {
25            // Extract the last digit
26            int currentDigit = n % 10;
27            n /= 10;
28          
29            // Get the word representation of the digit
30            const string& word = digitToWord.at(currentDigit);
31          
32            // Toggle bits for each letter in the word
33            for (char letter : word) {
34                // XOR toggles the bit: 0->1 if first occurrence, 1->0 if second, etc.
35                int bitPosition = letter - 'a';
36                letterFrequencyMask ^= (1 << bitPosition);
37            }
38        }
39      
40        // Count the number of set bits (letters with odd occurrence count)
41        return __builtin_popcount(letterFrequencyMask);
42    }
43};
44
1/**
2 * Counts the number of characters that appear an odd number of times
3 * in the English word representation of all digits in the given number
4 * @param n - The input number to process
5 * @returns The count of characters with odd occurrences
6 */
7function countOddLetters(n: number): number {
8    // Mapping of digits to their English word representations
9    const digitToWord: Record<number, string> = {
10        0: 'zero',
11        1: 'one',
12        2: 'two',
13        3: 'three',
14        4: 'four',
15        5: 'five',
16        6: 'six',
17        7: 'seven',
18        8: 'eight',
19        9: 'nine',
20    };
21
22    // Bitmask to track odd/even occurrences of each character
23    // Each bit position represents a letter (a-z)
24    let characterMask = 0;
25  
26    // Process each digit of the number from right to left
27    while (n > 0) {
28        // Extract the rightmost digit
29        const currentDigit = n % 10;
30        // Remove the rightmost digit from n
31        n = Math.floor(n / 10);
32      
33        // Process each character in the word representation of the digit
34        for (const character of digitToWord[currentDigit]) {
35            // Calculate the bit position for this character (0 for 'a', 1 for 'b', etc.)
36            const bitPosition = character.charCodeAt(0) - 'a'.charCodeAt(0);
37            // Toggle the bit at the calculated position using XOR
38            // If bit is 0 (even count), it becomes 1 (odd count)
39            // If bit is 1 (odd count), it becomes 0 (even count)
40            characterMask ^= 1 << bitPosition;
41        }
42    }
43
44    // Count the number of set bits (1s) in the mask
45    // Each set bit represents a character with odd occurrences
46    return bitCount(characterMask);
47}
48
49/**
50 * Counts the number of set bits (1s) in a 32-bit integer
51 * Uses Brian Kernighan's algorithm with bit manipulation optimizations
52 * @param i - The integer to count bits in
53 * @returns The number of set bits
54 */
55function bitCount(i: number): number {
56    // Step 1: Count bits in groups of 2
57    i = i - ((i >>> 1) & 0x55555555);
58  
59    // Step 2: Count bits in groups of 4
60    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
61  
62    // Step 3: Count bits in groups of 8
63    i = (i + (i >>> 4)) & 0x0f0f0f0f;
64  
65    // Step 4: Count bits in groups of 16
66    i = i + (i >>> 8);
67  
68    // Step 5: Count bits in groups of 32
69    i = i + (i >>> 16);
70  
71    // Return only the lowest 6 bits (max value is 32, which fits in 6 bits)
72    return i & 0x3f;
73}
74

Time and Space Complexity

Time Complexity: O(log n)

The algorithm iterates through each digit of the input number n. Since a number n has O(log n) digits (specifically ⌊log₁₀ n⌋ + 1 digits), the while loop executes O(log n) times. Within each iteration:

  • Extracting a digit using modulo and division operations takes O(1) time
  • Looking up the word representation in dictionary d takes O(1) time
  • The inner for loop iterates through the characters of the digit's word representation, which has at most 5 characters (for words like "three", "seven", "eight"), so this is O(1) time
  • The XOR operation and bit shift are O(1) operations

Therefore, the overall time complexity is O(log n) × O(1) = O(log n).

Space Complexity: O(1)

The algorithm uses:

  • A fixed-size dictionary d with 10 entries, which is O(1) space
  • A single integer variable mask to store the bit representation, which uses at most 26 bits (one for each letter a-z), so O(1) space
  • A few temporary variables (x, c) that use O(1) space

The space usage does not grow with the input size n, so the space complexity is O(1).

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

Common Pitfalls

1. Incorrect Digit Processing Order

A critical pitfall is misunderstanding how the digits are processed when using modulo operations. The code extracts digits from right to left (least significant to most significant), but the problem requires concatenating words in the order digits appear in the original number.

Example of the issue:

  • For n = 123, the code processes: 3 → 2 → 1
  • This gives us "three" + "two" + "one" = "threetwoone"
  • But the problem expects: "one" + "two" + "three" = "onetwothree"

Solution: Convert the number to a string first to preserve the digit order:

def countOddLetters(self, n: int) -> int:
    letter_parity_mask = 0
  
    # Convert to string to process digits in correct order
    for digit_char in str(n):
        digit = int(digit_char)
        word = DIGIT_TO_WORD[digit]
      
        for char in word:
            bit_position = ord(char) - ord("a")
            letter_parity_mask ^= 1 << bit_position
  
    return letter_parity_mask.bit_count()

2. Edge Case: n = 0

When n = 0, the while loop while n > 0: never executes, returning 0 instead of processing the single digit "zero".

Solution: Handle zero as a special case or use string conversion:

def countOddLetters(self, n: int) -> int:
    if n == 0:
        # "zero" has z=1, e=1, r=1, o=1 (4 odd characters)
        return 4
  
    letter_parity_mask = 0
    # Rest of the code...

3. Integer Overflow in Other Languages

While Python handles arbitrary precision integers, implementing this in languages like C++ or Java could cause issues with the bitmask for large inputs, especially if using int instead of long or appropriate bit containers.

Solution for other languages: Use appropriate data types (e.g., unsigned int or std::bitset<26> in C++) to ensure all 26 possible letter positions can be tracked.

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

How does merge sort divide the problem into subproblems?


Recommended Readings

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

Load More