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