1. 程式人生 > 其它 >Java n種方式分割統計單詞

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