image.png

思路:字典树 + 回溯 + 记忆化搜索

  • 此题的正解应该是动态规划,但是字典树也可以做
  • 对于wordDict中的每一个word,都需要直通根节点地放置在字典树当中。每个单词的地位是平等的。
  • 对于目标字符串s而言,如果读到了isEnd标志,意味着当前单词读完了,就重新从根节点开始找起,找下一个单词,不断重复这个操作,似乎可以找完整棵树。
  • 但是如果出现前缀相同的情况,比如app apple,先读到app的终结标志,直接就开始找下一个字符了,显然不对,所以需要回溯。
  • 但是回溯时间复杂度指数级增长,所以需要用failCase[i]来表示以第i位的字符开头,必然无法找到需要的单词,防止重复回溯。

image.png

代码:

  1. class TrieNode:
  2. def __init__(self):
  3. self.isEnd = False
  4. self.children = [None for _ in range(26)]
  5. def insert(self, wordDict: List[str]) -> None:
  6. # 把所有的字符串放到同一个字典当中
  7. # 根节点 - leet && 根节点 -- code
  8. trieRoot = self
  9. for word in wordDict:
  10. curNode = trieRoot
  11. for ch in word:
  12. chNum = ord(ch) - ord('a')
  13. if curNode.children[chNum] == None:
  14. curNode.children[chNum] = TrieNode()
  15. curNode = curNode.children[chNum]
  16. curNode.isEnd = True
  17. class Solution:
  18. def __init__(self):
  19. # 为记忆化搜索做准备
  20. self.failedCase = [False for _ in range(310)]
  21. def search(self, rootNode: TrieNode(), s: str, beginPos: int) -> bool:
  22. # 记忆化搜索,以beginPos开头的必然不在字典中,所以下次遍历到这个位置的时候直接返回
  23. if self.failedCase[beginPos]:
  24. return False
  25. if beginPos == len(s):
  26. return True
  27. curNode = rootNode
  28. while beginPos < len(s):
  29. chNum = ord(s[beginPos]) - ord('a')
  30. if curNode.children[chNum] == None:
  31. return False
  32. curNode = curNode.children[chNum]
  33. # 回溯
  34. if curNode.isEnd:
  35. if (self.search(rootNode, s, beginPos + 1)):
  36. return True
  37. else:
  38. self.failedCase[beginPos + 1] = True
  39. beginPos += 1
  40. return False
  41. def wordBreak(self, s: str, wordDict: List[str]) -> bool:
  42. rootNode = TrieNode()
  43. rootNode.insert(wordDict)
  44. return self.search(rootNode, s, 0)