Java n种方式分割统计单词
819. 最常见的单词
给定一个段落 (paragraph) 和一个禁用单词列表 (banned)。返回出现次数最多,同时不在禁用列表中的单词。
题目保证至少有一个词不在禁用列表中,而且答案唯一。
禁用列表中的单词用小写字母表示,不含标点符号。段落中的单词不区分大小写。答案都是小写字母。
**示例 **
输入: paragraph = "Bob hit a ball, the hit BALL flew far after it was hit." banned = ["hit"] 输出: "ball" 解释: "hit" 出现了3次,但它是一个禁用的单词。 "ball" 出现了2次 (同时没有其他单词出现2次),所以它是段落里出现次数最多的,且不在禁用列表中的单词。 注意,所有这些单词在段落里不区分大小写,标点符号需要忽略(即使是紧挨着单词也忽略, 比如 "ball,"), "hit"不是最终的答案,虽然它出现次数更多,但它在禁用单词列表中。 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/most-common-word 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
1 <= 段落长度 <= 1000
0 <= 禁用单词个数 <= 100
1 <= 禁用单词长度 <= 10
答案是唯一的, 且都是小写字母 (即使在 paragraph 里是大写的,即使是一些特定的名词,答案都是小写的。)
paragraph 只包含字母、空格和下列标点符号!?',;.
不存在没有连字符或者带有连字符的单词。
单词里只包含字母,不会出现省略号或者其他标点符号。
哈希表+计数的经典问题
关键在于正确的取出每个单词
遍历拼接单词
思路 :用Set来存被禁用的单词,用map来存储没被禁用的单词对应的出现次数
用一个字符串来存储单词,遍历每个字符,如果是小写字母就拼到字符串的后面,否则当前字符串要么为空,要么就已经是一个单词了
对这个单词进行统计次数,存入map
最后从map中找出value最大的key返回
时间复杂度分析:O(n)
空间复杂度分析:使用了Set和Map,还有一个临时保存单词的字符串,总的为O(n)
Java 版代码
class Solution {
public String mostCommonWord(String paragraph, String[] banned) {
Set<String> bannedSet = new HashSet<>();
Map<String,Integer> wordCnt = new HashMap<>();
// 把禁用单词装入Set
for (String bannedWord : banned) {
bannedSet.add(bannedWord);
}
char[] array = paragraph.toCharArray();
int maxCnt = 0;
StringBuilder curWord = new StringBuilder();
// 遍历次数为array.length + 1, 考虑最后一个字符为字母的特殊情况
for (int i = 0; i <= array.length; i++) {
if (i < array.length && Character.isLetter(array[i])) {
curWord.append(array[i]);
} else {
String curWordString = curWord.toString().toLowerCase();
if (curWord.length() > 0 && !bannedSet.contains(curWordString)) {
wordCnt.put(curWordString, wordCnt.getOrDefault(curWordString, 0) +1);
maxCnt = Math.max(maxCnt, wordCnt.get(curWordString));
}
// 清空StringBuilder
curWord.setLength(0);
}
}
for (Map.Entry<String, Integer> word :wordCnt.entrySet()) {
if (word.getValue() == maxCnt) {
return word.getKey();
}
}
return "";
}
}
双指针
思路 :整体思路与上面一致,区别在于不用一个临时的字符串来存储单词,而是用两个指针i , j
指向单词的开头和末尾
当j
指向字母时, i = j
; j
遍历直到指向非字母或者j == paragraph.length+1
这时的单词为 paragraph[i,j-1]
时间复杂度分析:O(n),少了拼接字符串的开销
空间复杂度分析:使用了Set和Map,还有一个临时保存单词的字符串,总的为O(n)
Java 版代码
class Solution {
public String mostCommonWord(String paragraph, String[] banned) {
Set<String> bannedSet = new HashSet<>();
Map<String,Integer> wordCnt = new HashMap<>();
for (String bannedWord : banned) {
bannedSet.add(bannedWord);
}
char[] array = paragraph.toCharArray();
String curWord = "";
int maxCnt = 0;
for (int i = 0, j = 0; j < array.length; j++) {
// array[j] 指向的是单词的首字母
if (Character.isLetter(array[j]) ) {
i = j;
// j遍历直到指向非字母或者j == paragraph.length+1
while(j < array.length && Character.isLetter(array[j])) {
j++;
}
// 这时的单词为 paragraph[i,j-1]
curWord = paragraph.substring(i,j).toLowerCase();
if (!bannedSet.contains(curWord)) {
wordCnt.put(curWord, wordCnt.getOrDefault(curWord, 0)+1);
maxCnt = Math.max(maxCnt, wordCnt.get(curWord));
}
}
}
System.out.println(wordCnt);
for (Map.Entry<String, Integer> word :wordCnt.entrySet()) {
if (word.getValue() == maxCnt) {
return word.getKey();
}
}
return "";
}
}
正则表达式和库函数
思路 :用正则表达式分割原字符串获得单词数组
用Collections.sort来获取map中的最大值
尽可能用封装好的函数简化代码
Java 版代码
class Solution {
public String mostCommonWord(String paragraph, String[] banned) {
// 将 paragraph 中的非空格特殊字符全部替换成空格,再按空格分割
String[] words = paragraph.replaceAll("\\W+", " ").toLowerCase().split(" ");
Set<String> bannedSet = new HashSet<>(Arrays.asList(banned));
Map<String, Integer> map = new HashMap<>();
for (String word : words) {
if (!bannedSet.contains(word)) {
map.put(word, map.getOrDefault(word, 0) + 1);
}
}
return Collections.max(map.entrySet(), Map.Entry.comparingByValue()).getKey();
}
}
Stream流处理
思路:用jdk1.8 提供的Stream流处理,写出高效率、干净、简洁的代码。
忽略实现,只关注业务
class Solution {
public String mostCommonWord(String paragraph, String[] banned) {
Set<String> bannedSet = Arrays.stream(banned).collect(Collectors.toSet());
String[] words = paragraph.replaceAll("\\W+", " ").toLowerCase().split(" ");
return Arrays.stream(words)
// 过滤bannedSet中的单词
.filter(word->!bannedSet.contains(word))
// 按照 word 分类,并统计个数生成 map
.collect(Collectors.groupingBy(word->word, Collectors.counting()))
// 将map的entrySet 转成流
.entrySet().stream()
// 获得value最大的Optional
.max(Map.Entry.comparingByValue())
// get 获得Entry, getKey 获得Entry 的key
.get().getKey();
}
}
参考资料
《深度解析Lambda表达式和Stream表达式的使用原理》https://www.jianshu.com/p/40025df7913c
《Stream类的collect方法详解》https://www.jianshu.com/p/ccbb42ad9551