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

posted @ 2022-05-08 09:52  油虾条  阅读(321)  评论(0编辑  收藏  举报