Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1002. Find Common Characters - Leetcode Solution

Code Implementation

from typing import List
from collections import Counter

class Solution:
    def commonChars(self, words: List[str]) -> List[str]:
        # Start with the letter counts of the first word
        common = Counter(words[0])
        for word in words[1:]:
            # Intersect with the counts from the next word
            common &= Counter(word)
        # Expand the counts back into a list of characters
        result = []
        for char, count in common.items():
            result.extend([char] * count)
        return result
      
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

class Solution {
public:
    vector<string> commonChars(vector<string>& words) {
        vector<int> minFreq(26, INT_MAX);
        for (const string& word : words) {
            vector<int> freq(26, 0);
            for (char c : word) {
                freq[c - 'a']++;
            }
            for (int i = 0; i < 26; ++i) {
                minFreq[i] = min(minFreq[i], freq[i]);
            }
        }
        vector<string> res;
        for (int i = 0; i < 26; ++i) {
            for (int j = 0; j < minFreq[i]; ++j) {
                res.push_back(string(1, i + 'a'));
            }
        }
        return res;
    }
};
      
import java.util.*;

class Solution {
    public List<String> commonChars(String[] words) {
        int[] minFreq = new int[26];
        Arrays.fill(minFreq, Integer.MAX_VALUE);
        for (String word : words) {
            int[] freq = new int[26];
            for (char c : word.toCharArray()) {
                freq[c - 'a']++;
            }
            for (int i = 0; i < 26; i++) {
                minFreq[i] = Math.min(minFreq[i], freq[i]);
            }
        }
        List<String> result = new ArrayList<>();
        for (int i = 0; i < 26; i++) {
            for (int j = 0; j < minFreq[i]; j++) {
                result.add(String.valueOf((char)(i + 'a')));
            }
        }
        return result;
    }
}
      
/**
 * @param {string[]} words
 * @return {string[]}
 */
var commonChars = function(words) {
    let minFreq = new Array(26).fill(Infinity);
    for (let word of words) {
        let freq = new Array(26).fill(0);
        for (let c of word) {
            freq[c.charCodeAt(0) - 97]++;
        }
        for (let i = 0; i < 26; i++) {
            minFreq[i] = Math.min(minFreq[i], freq[i]);
        }
    }
    let res = [];
    for (let i = 0; i < 26; i++) {
        for (let j = 0; j < minFreq[i]; j++) {
            res.push(String.fromCharCode(i + 97));
        }
    }
    return res;
};
      

Problem Description

Given an array of strings words, where each string consists of lowercase English letters, your task is to find all characters that appear in every string (including duplicates). The result should be a list of single-character strings, each representing a character that is common to all words, as many times as it appears in every word. For example, if a character appears 2 times in every word, include it 2 times in the result. The order of the result does not matter.

Thought Process

To solve this problem, the first instinct might be to check each character in the first word and see if it appears in all other words, keeping track of how many times it appears. However, we need to ensure that we count duplicates correctly and do not over-count any character. Instead of using brute-force character checks, we can optimize this by counting occurrences and keeping track of the minimum number of times each character appears across all words. This leads us to think about using frequency counts (like hash maps or arrays) to efficiently track and compare character counts.

Solution Approach

  • Step 1: For each word, count the frequency of each character. Since we are dealing only with lowercase English letters, we can use an array of size 26 for efficiency.
  • Step 2: Initialize a frequency array (or hash map) with the counts from the first word.
  • Step 3: For each subsequent word, update the frequency array to be the minimum of the current value and the count in the current word. This way, we only keep track of characters that are common to all words and their minimum occurrence.
  • Step 4: After processing all words, the frequency array will contain, for each character, the number of times it appears in every word. If the value is greater than zero, add that character to the result as many times as the value.
  • Design Choice: We use arrays instead of hash maps for the character counts because the character set is small and fixed (26 lowercase letters), making array indexing more efficient and straightforward.

Example Walkthrough

Consider the input words = ["bella", "label", "roller"]:
  • First, count the characters in "bella": b=1, e=1, l=2, a=1
  • For "label": l=1, a=1, b=1, e=1, the minimum with previous is b=1, e=1, l=1, a=1
  • For "roller": r=2, o=1, l=2, e=1; the minimum with previous is l=1, e=1 (since b and a are missing or zero)
  • So, the answer is ["e", "l"]
  • Each step, we only keep the minimum count for each letter, ensuring that only characters present in all words are included, and as many times as they appear in every word.

Time and Space Complexity

  • Brute-force approach: Checking each character of the first word against all other words could lead to O(N * M * L), where N is the number of words, M is the length of the first word, and L is the average length of other words.
  • Optimized approach: Counting frequencies uses O(N * K), where N is the number of words and K is the length of each word. Since we use arrays of size 26, space complexity is O(1) for the counts (since 26 is constant), and O(N * K) for processing the words.
  • Overall, both time and space are efficient due to the fixed alphabet size.

Summary

The key insight is to use frequency counts and update them by taking the minimum across all words. This ensures that only common characters, and the correct number of their occurrences, are included in the result. The solution is elegant because it leverages the small, fixed character set and avoids unnecessary repeated checks, leading to an efficient and clean implementation.