Skip to content
JZLeetCode
Go back

LeetCode 692 Top K Frequent Words

Table of contents

Open Table of contents

Description

Question Links: LeetCode 692

Given an array of strings words and an integer k, return the k most frequent strings.

Return the answer sorted by the frequency from highest to lowest. Sort the words with the same frequency by their lexicographical order.

Example 1:

Input: words = ["i","love","leetcode","i","love","coding"], k = 2
Output: ["i","love"]
Explanation: "i" and "love" are the two most frequent words.
Note that "i" comes before "love" due to a lower alphabetical order.

Example 2:

Input: words = ["the","day","is","sunny","the","the","the","sunny","is","is"], k = 4
Output: ["the","is","sunny","day"]
Explanation: "the", "is", "sunny" and "day" are the four most frequent words,
with the number of occurrence being 4, 3, 2 and 1 respectively.

Constraints:

1 <= words.length <= 500
1 <= words[i].length <= 10
words[i] consists of lowercase English letters.
k is in the range [1, The number of unique words[i]]

Follow-up: Could you solve it in O(n log k) time and O(n) extra space?

Solution 1: Heap

Idea

Count word frequencies with a hash map, then use a max-heap ordered by (frequency descending, word ascending). Pop k times to get the result. For the follow-up O(n log k): use a min-heap of size k with a comparator that evicts the least desirable element (lower frequency, or higher lex order for ties), then reverse the result at the end.

Input: words = ["i","love","leetcode","i","love","coding"], k = 2

Step 1 - Count frequencies:
  count = {"i":2, "love":2, "leetcode":1, "coding":1}

Step 2 - Max-heap ordered by (-freq, word):
  push (-2,"i"), (-2,"love"), (-1,"coding"), (-1,"leetcode")

  Heap (max-heap, smallest tuple on top with negated freq):
       (-2,"i")
      /        \
  (-2,"love")  (-1,"coding")
    /
  (-1,"leetcode")

Step 3 - Pop k=2 times:
  pop (-2,"i")    -> result = ["i"]
  pop (-2,"love") -> result = ["i","love"]

Output: ["i","love"]

Complexity: Time O(n+mlogm)O(n + m \log m) where mm is the number of unique words — counting is O(n)O(n), building the heap is O(mlogm)O(m \log m), popping kk times is O(klogm)O(k \log m). For the min-heap variant: O(nlogk)O(n \log k). Space O(n)O(n) for the count map.

Java

public static List<String> topKFrequent(String[] words, int k) {
    Map<String, Integer> freq = new HashMap<>(); // O(n) space
    for (String w : words) freq.merge(w, 1, Integer::sum); // O(n) time
    // max-heap: higher freq first; same freq -> lexicographically smaller first
    PriorityQueue<Map.Entry<String, Integer>> maxHeap = new PriorityQueue<>(
            (a, b) -> a.getValue().equals(b.getValue())
                    ? a.getKey().compareTo(b.getKey()) // O(1) lex compare (word len <= 10)
                    : b.getValue() - a.getValue()      // descending frequency
    );
    maxHeap.addAll(freq.entrySet()); // O(m log m) where m = unique words
    List<String> result = new ArrayList<>(k);
    for (int i = 0; i < k; i++) result.add(maxHeap.poll().getKey()); // O(k log m)
    return result;
}

Python

class Solution:
    def topKFrequent(self, words: list[str], k: int) -> list[str]:
        count = Counter(words)  # O(n) time and space
        heap: list[tuple[int, str]] = []
        for word, freq in count.items():  # O(n log k): iterate n unique, each heap op log k
            heappush(heap, (-freq, word))  # negate freq for max-heap behavior
        res = []
        for _ in range(k):  # O(k log n)
            res.append(heappop(heap)[1])
        return res

C++

vector<string> topKFrequent(vector<string> &words, int k) {
    unordered_map<string, int> count;
    for (auto &w : words) count[w]++; // O(n) frequency count
    // min-heap: lower freq on top; same freq -> reverse lex on top (evict first)
    auto cmp = [](const pair<int, string> &a, const pair<int, string> &b) {
        if (a.first != b.first) return a.first > b.first; // higher freq = higher priority (stays)
        return a.second < b.second;                        // lower lex = higher priority (stays)
    };
    priority_queue<pair<int, string>, vector<pair<int, string>>, decltype(cmp)> pq(cmp);
    for (auto &[word, freq] : count) { // O(n log k) heap maintenance
        pq.emplace(freq, word);
        if ((int) pq.size() > k) pq.pop(); // evict least important
    }
    vector<string> res(k);
    for (int i = k - 1; i >= 0; i--) { // O(k log k) pop in reverse order
        res[i] = pq.top().second;
        pq.pop();
    }
    return res;
}

Rust

#[derive(Eq, PartialEq)]
struct WordFreq { word: String, freq: usize }

impl Ord for WordFreq {
    fn cmp(&self, other: &Self) -> Ordering {
        self.freq.cmp(&other.freq) // higher frequency wins
            .then_with(|| other.word.cmp(&self.word)) // lex smaller wins (reverse string cmp)
    }
}
impl PartialOrd for WordFreq {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

pub fn top_k_frequent(words: Vec<String>, k: i32) -> Vec<String> {
    let k = k as usize;
    let mut count: HashMap<String, usize> = HashMap::new();
    for word in &words { *count.entry(word.clone()).or_insert(0) += 1; } // O(n)
    let mut heap: BinaryHeap<WordFreq> = BinaryHeap::new(); // max-heap by (freq, reverse-lex)
    for (word, freq) in count { heap.push(WordFreq { word, freq }); } // O(m log m)
    let mut res = Vec::with_capacity(k);
    for _ in 0..k { res.push(heap.pop().unwrap().word); } // O(k log m)
    res
}

Solution 2: Bucket Sort

Idea

Count frequencies, distribute words into buckets indexed by their frequency, sort each bucket lexicographically, and collect from the highest-frequency bucket downward until we have k words.

Input: words = ["the","day","is","sunny","the","the","the","sunny","is","is"], k = 4

Step 1 - Count: {"the":4, "day":1, "is":3, "sunny":2}

Step 2 - Buckets (index = frequency):
  buckets[1] = ["day"]
  buckets[2] = ["sunny"]
  buckets[3] = ["is"]
  buckets[4] = ["the"]

Step 3 - Collect from highest bucket, sort each lex:
  freq=4: ["the"]           -> result = ["the"]
  freq=3: ["is"]            -> result = ["the","is"]
  freq=2: ["sunny"]         -> result = ["the","is","sunny"]
  freq=1: ["day"]           -> result = ["the","is","sunny","day"]  <- k=4, done!

Output: ["the","is","sunny","day"]

Complexity: Time O(n)O(n) for counting and bucketing, O(LlogL)O(L \log L) for sorting within each bucket where LL is the bucket size. Space O(n)O(n) for the count map and bucket array.

Java

public static List<String> topKFrequentBucket(String[] words, int k) {
    Map<String, Integer> freq = new HashMap<>(); // O(n) space
    for (String w : words) freq.merge(w, 1, Integer::sum); // O(n) time
    int maxFreq = Collections.max(freq.values()); // O(m)
    @SuppressWarnings("unchecked")
    List<String>[] buckets = new List[maxFreq + 1]; // index = frequency
    for (var e : freq.entrySet()) { // O(m) distribute
        int f = e.getValue();
        if (buckets[f] == null) buckets[f] = new ArrayList<>();
        buckets[f].add(e.getKey());
    }
    List<String> result = new ArrayList<>(k);
    for (int i = maxFreq; i >= 1 && result.size() < k; i--) { // collect from highest freq
        if (buckets[i] == null) continue;
        Collections.sort(buckets[i]); // O(L log L) lex sort within bucket
        for (String w : buckets[i]) {
            result.add(w);
            if (result.size() == k) break;
        }
    }
    return result;
}

Python

class Solution2:
    def topKFrequent(self, words: list[str], k: int) -> list[str]:
        count = Counter(words)  # O(n) time and space
        n = len(words)
        buckets: list[list[str]] = [[] for _ in range(n + 1)]  # O(n) space, index = frequency
        for word, freq in count.items():  # O(m) distribute, m unique words
            buckets[freq].append(word)
        res = []
        for freq in range(n, 0, -1):  # O(n) collect from highest freq
            bucket = sorted(buckets[freq])  # sort lexicographically within same frequency
            for word in bucket:
                res.append(word)
                if len(res) == k:
                    return res
        return res

C++

vector<string> topKFrequent(vector<string> &words, int k) {
    unordered_map<string, int> count;
    for (auto &w : words) count[w]++; // O(n) frequency count
    int n = (int) words.size();
    vector<vector<string>> buckets(n + 1); // index = frequency, O(n) space
    for (auto &[word, freq] : count) buckets[freq].push_back(word); // O(m) distribute
    vector<string> res;
    for (int freq = n; freq > 0 && (int) res.size() < k; freq--) {
        sort(buckets[freq].begin(), buckets[freq].end()); // O(L log L) lex sort per bucket
        for (auto &w : buckets[freq]) {
            res.push_back(w);
            if ((int) res.size() == k) return res;
        }
    }
    return res;
}

Rust

pub fn top_k_frequent_bucket(words: Vec<String>, k: i32) -> Vec<String> {
    let k = k as usize;
    let n = words.len();
    let mut count: HashMap<String, usize> = HashMap::new();
    for word in &words { *count.entry(word.clone()).or_insert(0) += 1; } // O(n)
    let mut buckets: Vec<Vec<String>> = vec![vec![]; n + 1]; // index = frequency
    for (word, freq) in count { buckets[freq].push(word); } // O(m) distribute
    let mut res = Vec::with_capacity(k);
    for freq in (1..=n).rev() { // collect from highest freq bucket
        buckets[freq].sort(); // sort each bucket lexicographically
        for word in &buckets[freq] {
            res.push(word.clone());
            if res.len() == k { return res; }
        }
    }
    res
}
Share this post on:

Next Post
System Design - How the ELF Binary Format and Program Loading Work