Skip to content

Commit 843d463

Browse files
feat: add TopKFrequentWords with deterministic tie-breaking
1 parent 4b04ad4 commit 843d463

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.thealgorithms.strings;
2+
3+
import java.util.ArrayList;
4+
import java.util.Comparator;
5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
/**
10+
* Utility class to find the top-k most frequent words.
11+
*
12+
* <p>Words are ranked by frequency in descending order. For equal frequencies,
13+
* words are ranked in lexicographical ascending order.
14+
*
15+
*/
16+
public final class TopKFrequentWords {
17+
private TopKFrequentWords() {
18+
}
19+
20+
/**
21+
* Finds the k most frequent words.
22+
*
23+
* @param words input array of words
24+
* @param k number of words to return
25+
* @return list of top-k words ordered by frequency then lexicographical order
26+
* @throws IllegalArgumentException if words is null, k is negative, or words contains null
27+
*/
28+
public static List<String> findTopKFrequentWords(String[] words, int k) {
29+
if (words == null) {
30+
throw new IllegalArgumentException("Input words array cannot be null.");
31+
}
32+
if (k < 0) {
33+
throw new IllegalArgumentException("k cannot be negative.");
34+
}
35+
if (k == 0 || words.length == 0) {
36+
return List.of();
37+
}
38+
39+
Map<String, Integer> frequency = new HashMap<>();
40+
for (String word : words) {
41+
if (word == null) {
42+
throw new IllegalArgumentException("Input words cannot contain null values.");
43+
}
44+
frequency.put(word, frequency.getOrDefault(word, 0) + 1);
45+
}
46+
47+
List<String> candidates = new ArrayList<>(frequency.keySet());
48+
candidates.sort(
49+
Comparator.<String>comparingInt(frequency::get)
50+
.reversed()
51+
.thenComparing(Comparator.naturalOrder())
52+
);
53+
54+
int limit = Math.min(k, candidates.size());
55+
return new ArrayList<>(candidates.subList(0, limit));
56+
}
57+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.List;
7+
import java.util.stream.Stream;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.Arguments;
10+
import org.junit.jupiter.params.provider.MethodSource;
11+
12+
class TopKFrequentWordsTest {
13+
14+
@ParameterizedTest
15+
@MethodSource("validTestCases")
16+
void testFindTopKFrequentWords(String[] words, int k, List<String> expected) {
17+
assertEquals(expected, TopKFrequentWords.findTopKFrequentWords(words, k));
18+
}
19+
20+
static Stream<Arguments> validTestCases() {
21+
return Stream.of(
22+
Arguments.of(
23+
new String[] {"i", "love", "leetcode", "i", "love", "coding"},
24+
2,
25+
List.of("i", "love")
26+
),
27+
Arguments.of(
28+
new String[] {"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"},
29+
4,
30+
List.of("the", "is", "sunny", "day")
31+
),
32+
Arguments.of(
33+
new String[] {"bbb", "aaa", "bbb", "aaa", "ccc"},
34+
2,
35+
List.of("aaa", "bbb")
36+
),
37+
Arguments.of(
38+
new String[] {"one", "two", "three"},
39+
10,
40+
List.of("one", "three", "two")
41+
),
42+
Arguments.of(new String[] {}, 3, List.of()),
43+
Arguments.of(new String[] {"x", "x", "y"}, 0, List.of())
44+
);
45+
}
46+
47+
@ParameterizedTest
48+
@MethodSource("invalidTestCases")
49+
void testFindTopKFrequentWordsInvalidInput(String[] words, int k) {
50+
assertThrows(IllegalArgumentException.class, () -> TopKFrequentWords.findTopKFrequentWords(words, k));
51+
}
52+
53+
static Stream<Arguments> invalidTestCases() {
54+
return Stream.of(
55+
Arguments.of((String[]) null, 1),
56+
Arguments.of(new String[] {"a", null, "b"}, 2),
57+
Arguments.of(new String[] {"a"}, -1)
58+
);
59+
}
60+
}

0 commit comments

Comments
 (0)