各位题友大家好! 今天是 @负雪明烛 坚持日更的第 33 天。今天力扣上的每日一题是「1178. 猜字谜」。
解题思路
本文的两个重点:
- 把每个字符串用二进制数字表示(状态压缩)
- 寻找所有子集(subset)
大家好,今天的题虽然是 Hard,但是大家不要怕,本题解把难度降为了 Medium,肯定让你看懂。
首先让所有 words
和 puzzle
两两匹配肯定是不行的,时间复杂度到了 $O(M * N) = 10 ^ 9$,会超时。
一个简单的思路是,对于每个 puzzle
没有必要遍历所以 words
,只用找符合条件的 words
出现了多少次就行了。这就是很多题解的思路:状态压缩。
题目给出了两个条件:
- 单词
word
中包含谜面puzzle
的第一个字母。 - 单词
word
中的每一个字母都可以在谜面puzzle
中找到。
第一步,状态压缩
注意题目的第二个条件只要求能找到(出现一次即可),对出现次数没要求。为了解决这个问题,我们可以使用二进制数字来表每个 word 和 puzzle
,该二进制数字就是 word 和 puzzle
的特征。这道题只包含26个小写字母,可以压缩到一个 int 中。int 中的每一位取0和1表示字符是否出现过。比如 “aabb” 可以用 11 表示,”accc” 可以用 101 表示。
可以看出不同的单词可能映射成同一个数字,比如 “aabbb” 和 “ab” 都映射成了 11。这就是状态压缩。
把每个word都做状态压缩,用freq保存每个二进制数字出现的次数。
第二步,匹配
我们要用puzzle[0] + subset(puzzle[1:N - 1])
对应的二进制数字去跟 word 的二进制做匹配。
很多题解都在讨论二进制表示下的 subset 怎么求,我觉得都不好理解,直接做一下「78. 子集」不就得了?暴力求出每个puzzle[1:N - 1]的全排列,然后计算每个排列下puzzle[0] + subset(puzzle[1:N - 1])
对应的二进制数字。
题目说了 puzzle 的长度为 7 位,subset(puzzle[1:N - 1]) 的是时间复杂度为 $O(2 ^ N)$ = $2 ^ 6 = 64$ 次计算,比较快。
求出puzzle[0] + subset(puzzle[1:N - 1])
对应的二进制数字之后,累加 freq 中出现的次数,就是该puzzle对应的word有多少。
代码
Python 代码如下,直接用了 78 题的求 subset 代码。
class Solution:
def findNumOfValidWords(self, words: List[str], puzzles: List[str]) -> List[int]:
freq = collections.Counter()
for word in words:
mask = 0
for c in word:
mask |= 1 << (ord(c) - ord('a'))
freq[mask] += 1
res = []
for puzzle in puzzles:
total = 0
for perm in self.subsets(puzzle[1:]):
mask = 1 << (ord(puzzle[0]) - ord('a'))
for c in perm:
mask |= 1 << (ord(c) - ord('a'))
total += freq[mask]
res.append(total)
return res
def subsets(self, words: List[int]) -> List[List[int]]:
res = [""]
for i in words:
res = res + [i + word for word in res]
return res
- 时间复杂度:O(M + N)。
- 空间复杂度:O(M)。
刷题心得
不畏浮云遮望眼,透过现象看本质。
OK,以上就是 @负雪明烛 写的今天题解的全部内容了,如果你觉得有帮助的话,求赞、求关注、求收藏。如果有疑问的话,请在下面评论,我会及时解答。
关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。
祝大家牛年大吉!AC 多多,Offer 多多!我们明天再见!