【LeetCode 每日一题】1935. 可以输入的最大单词数
Problem: 1935. 可以输入的最大单词数
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N + M)
- 空间复杂度:O(N + M)
整体思路
这段代码的目的是计算在一个给定的文本 text
中,有多少个单词是可以被完整打出的。判断的标准是,这个单词不包含任何 brokenLetters
字符串中指定的“损坏”的字母。
该算法采用了一种清晰、分步的策略:分割 -> 预处理 -> 逐词检查。
-
第一步:分割文本为单词
String[] words = text.split(" ");
:算法首先将输入的text
字符串按照空格进行分割,得到一个包含所有单词的字符串数组words
。
-
第二步:预处理损坏的字母
- 为了能够快速地判断一个字母是否是“损坏”的,算法将
brokenLetters
字符串中的所有字符存入一个 哈希集合 (HashSet)broken
。 - 目的:
HashSet
提供了平均时间复杂度为 O(1) 的contains()
查找操作,这远比在brokenLetters
字符串中进行线性搜索要高效得多。
- 为了能够快速地判断一个字母是否是“损坏”的,算法将
-
第三步:逐词检查并计数
- 算法采用了一种“减法”的计数策略。
- 初始化:
int ans = words.length;
,首先假设所有单词都可以被打出。 - 遍历:然后,通过一个
for-each
循环,遍历words
数组中的每一个word
。 - 逐字检查:对于每个
word
,再进入一个内层循环,遍历该单词的每一个字符c
。 - 判断与中断:
if (broken.contains(c))
: 使用预处理好的broken
集合,快速检查当前字符c
是否是损坏的。- 如果
c
是损坏的,那么整个word
就无法被打出。此时,将计数器ans
减一 (ans--
)。 break;
: 一旦发现一个损坏的字母,就没有必要再检查这个单词的其余字母了。break
语句会立即跳出内层循环,开始检查下一个单词,这是一个有效的优化。
- 如果一个单词的所有字符都被检查完而没有触发
break
,那么ans
的值就不会因这个单词而改变,它仍然被算作一个可以打出的单词。
-
返回结果
- 在检查完所有单词后,
ans
中存储的就是最终可以被打出的单词的总数。
- 在检查完所有单词后,
完整代码
import java.util.HashSet;
import java.util.Set;class Solution {/*** 计算文本中有多少个单词可以被完整打出。* @param text 输入的文本字符串* @param brokenLetters 包含所有损坏字母的字符串* @return 可以被打出的单词数量*/public int canBeTypedWords(String text, String brokenLetters) {// 步骤 1: 将文本按空格分割成单词数组String[] words = text.split(" ");// 步骤 2: 预处理损坏的字母,存入 HashSet 以便快速查找Set<Character> broken = new HashSet<>();for (char c : brokenLetters.toCharArray()) {broken.add(c);}// 步骤 3: 初始化计数器为总单词数,然后减去无法打出的单词int ans = words.length;// 遍历每一个单词for (String word : words) {// 遍历当前单词的每一个字符for (char c : word.toCharArray()) {// 使用 HashSet 进行 O(1) 的快速检查if (broken.contains(c)) {// 如果单词中包含任何一个损坏的字母,则该单词无法打出ans--; // 将计数器减一break; // 无需再检查此单词的其余字母,跳出内层循环}}}return ans;}
}
时空复杂度
- 设
N
为text
的总长度。 - 设
M
为brokenLetters
的长度。 - 设
W
为text
中的单词数。 - 设
L
为最长单词的长度。
时间复杂度:O(N + M)
- 分割字符串:
text.split(" ")
的时间复杂度与文本长度N
成正比,即 O(N)。 - 构建
broken
集合:遍历brokenLetters
并将其字符加入HashSet
。时间复杂度为 O(M)。 - 检查单词:
- 外层循环遍历
W
个单词。 - 内层循环遍历每个单词的字符。
word.toCharArray()
的时间与单词长度成正比。broken.contains(c)
是 O(1) 操作。- 所有内层循环的总执行次数,等于
text
中所有非空格字符的总数。这个总数小于等于N
。 - 因此,整个检查过程的时间复杂度与
text
的总长度N
成正比,即 O(N)。
- 外层循环遍历
综合分析:
总的时间复杂度是 O(N) + O(M) + O(N) = O(2N + M)。简化后,可以表示为 O(N + M)。
空间复杂度:O(N + M)
words
数组:text.split(" ")
会创建一个新的字符串数组。在最坏的情况下(例如,每个字符都是一个单词,用空格隔开),这个数组可能需要存储大约N/2
个字符串对象。总的空间占用与text
的长度N
相关,为 O(N)。broken
集合:HashSet
需要存储brokenLetters
中的所有唯一字符。其空间复杂度为 O(M)。toCharArray()
副本:在循环中,word.toCharArray()
会为每个单词创建一个临时的字符数组副本。由于这个空间是可复用的,所以它只影响峰值内存使用,其大小为O(L)
(最长单词长度)。
综合分析:
算法所需的额外空间主要由 words
数组和 broken
集合决定。因此,总的空间复杂度为 O(N + M)。