lc上有这样关键词含有word
的两两成组的八道题, 分别是word break I&II
, word pattern I&II
, word ladder I&II
, word search I&&II
.
今晚重新集中做了这几道题, 然后记录一下解析.
139. word break I
描述
给一个非空字符串s
和一个word字典wordDict
, 检查s
是否能由wordDict
里的word
(相当于substring)拼接成. 每个word
可以用0次或多次.
例如s = "leetcode", wordDict = ["leet", "code"]
, s
可以由leet
和code
拼接成.
思路
经典dp, 用一个数组dp[]
, dp[i]
表示s[:i]
这个substring能否被拼接出来. 初始值dp[0] = True
空字符串默认一定能拼接出来.
对于某个i
, 如果s[i+len(word)] == word
并且 dp[i] == True
, 那么当前字符串可以被拼接到i+len(word)
的位置, 也就是dp[i+len(word)]
为True
.
代码
1 | class Solution(object): |
140. word break II
描述
题意基本和word break I
一样, 不同的是这个题要求返回所有可能的拼接结果. 不同的word
用空格隔开.
思路
一个思路绝对正确并且(理论上)最优的bottom up方法是, 用dp数组(其实是字典)来存处理到当前位置i
的时候, 对于substrings[:i]
的所有可能拼接结果. 然后每次每次match到新的word
, 加入对应新位置i+len(word)
的结果中去.
但是test case有一个非常强…会把这种方法卡掉. 分别试了试正向和反向的bottom up, 都会挂在一个特殊case上(长这样: aaaaaaa...b
, 候选字典全是各种长度的a
, 因为没有b
, 肯定不行)上. 一个方法是先用上个题的方法check一下是否可分, 如果不可分直接返回空list. 或者用top down.
top down的理论上要慢一些, 因为函数上下文切换和求hash(本题的bottom up也求了hash, 不过可以用list of list解决避免hash). 但是bottom up在上面那个case会出现超级多的不同组合, 导致TLE/MLE
.
代码
top down(反向)
1 | class Solution(object): |
bottom up(反向), MLE
1 | class Solution(object): |
290. word pattern I
描述
给一个pattern
和str
(空格隔开的一堆word
), 判断是否同构.
例如pattern = "abba", str = "dog cat cat dog"
就是同构的, a -> dog
, b -> cat
. pattern = "aaaa", str = "dog cat cat dog"
因为match不了就不是同构.
思路
水题, 直接按顺序同步处理pattern
和str
, assign一堆id
就行. 为了缩短代码长度默认取最晚出现的坐标
代码
1 | class Solution(object): |
或者这样也行…更短, 就是复杂度高了.
1 | class Solution(object): |
291. word pattern II
描述
和上一个题不同的一点是, str
中不再含有空格. 我们需要自行决定在哪里分割开来匹配pattern
串. 只能暴力搜索.
例如
pattern = "abab", str = "redblueredblue"
, return:true
pattern = pattern = "aaaa", str = "asdasdasdasd"
, return:true
pattern = "aabb", str = "xyzabcxzyabc"
, returnfalse
pattern = "ab", str = "xx"
, returnfalse
(从这可以看出来pattern
和str
是严格的双向映射)
思路
今年1月面pony.ai
的时候准备过这个题, 当时被难的稀里哗啦抄答案也抄的一知半解…不过最近做的多了而且做过736
这种更复杂的token
相关的题, 终于能快速ac了.
- 用一个字典
p2s
表示当前的pattern
到str
的映射, 另一个字典s2p
表示逆映射(主要是为了对付ab
和xx
这种情况,x
被用来和a
匹配就不能再用来匹配b
了). - 如果当前
pattern[i]
存在于p2s
中, 说明被映射过了, 现在只能用之前已经存入的值p2s[pattern[i]]
. 如果这个值和目前str
的开头匹配不上, 直接returnFalse
. 否则分别移动pattern
和str
递归进行. - 如果当前
pattern[i]
不存在于p2s
中, 这是一个新的pattern
字符, 没有被用过, 可以用来匹配任意的str
字符串. 那么枚举一下可能的substring(并且str
的substring同样不能被映射过), 并递归调用. 注意更新两个字典.
代码
1 | class Solution(object): |
127. word ladder I
描述
给一个起始word
和终止word
, 还有一个梯子wordDict
. 每次更新一个字符, 最少几步从起始更新到终止.
思路
建一个跳转表, 然后层序BFS就行…不过还是WA了两发… 因为我上面描述就是错的…它要返回的是最短序列, 也就是更新步数+1
:)
代码
1 | class Solution(object): |
126. word ladder II
描述
和上一题一样, 这次要返回所有最短(变换)序列.
思路
依旧是BFS, 往队列里塞值的时候多加一个path
. 然后因为路径(可能)有交叉, visited
集合(我习惯用seen
)不能在每次word
入队时立刻更新, 否则会阻断未来的通路. 但是可以在pop出这个word
时更新, 因为如果不更新, 下次再遇到本次这个word
, 所产生的结果一定是更长的(invalid).
当第一次遇到endWord
时, 当前的len(path)
就是最短路径. 下次遇到更长的立刻break出去.
代码
1 | class Solution(object): |
79. word search I
描述
给一个字符棋盘, 和一个word
, 检查它在不在这个棋盘里. 路径不能重叠.
思路
标准DFS. 注意当前正在使用的字符要mask掉, 如果本轮搜索失败, 返回时复原.
代码
1 | class Solution(object): |
212. word search II
描述
和上一题不同的是, 这次要搜多个word
. 然后返回存在于棋盘中的那些.
思路
还想上一个题那样逐一处理会TLE. 因为没有利用已经有的信息. 比如要找abcde
和abc
, 在找 abcde
的过程中顺便就把abc
找到了, 而不用退出来再找一遍abc
.
考虑到这一点, 用trie
来处理. 把所有word
插入trie
, 完成后, trie
的最外层的节点都是不同word
的入口点, 当然也有些word
的入口点包含在trie
的路径中, 比如abcde
和bcd
和cde
.
代码
1 | class Solution(object): |