返回

高效学习LeetCode 139 和 140:深入解析DFS + memo 算法

闲谈

LeetCode 139 与 140:动态规划算法中的璀璨明珠

在浩瀚的动态规划算法世界里,LeetCode 139 和 140 犹如两颗耀眼的明珠,闪耀着夺目的光芒。这两道题不仅考验了算法爱好者的基本功,更展示了巧妙的优化技巧,值得我们深入探究。

LeetCode 139:单词拆分

问题

我们有一个字符串 s 和一本字典 wordDict,包含一组单词。我们的目标是判断 s 是否可以拆分成一个或多个字典中的单词,并且这些单词之间用空格分隔。

方法 1:DFS + 备忘录

DFS + 备忘录算法是解决 LeetCode 139 的经典方法。我们使用深度优先搜索(DFS)枚举所有可能的单词拆分方案,并用备忘录记录已经计算过的结果,避免重复计算。

def isWordBreak(s: str, wordDict: List[str], memo={}):
  if s in memo:
    return memo[s]
  
  if not s:
    return True
  
  for word in wordDict:
    if s.startswith(word):
      if isWordBreak(s[len(word):], wordDict, memo):
        memo[s] = True
        return True
  
  memo[s] = False
  return False

优化:

  • 对字典按单词长度从小到大排序,避免不必要的遍历。
  • 使用滚动数组存储结果,减少空间复杂度。

LeetCode 140:单词拆分 II

问题:

在 LeetCode 139 的基础上,LeetCode 140 要求我们返回所有可能的单词拆分方案。

方法 1:DFS

def wordBreak(s: str, wordDict: List[str]):
  result = []

  def dfs(start, path):
    if start == len(s):
      result.append(path)
      return
    
    for i in range(start + 1, len(s) + 1):
      word = s[start:i]
      if word in wordDict:
        dfs(i, path + [word])
  
  dfs(0, [])

  return result

优化:

  • 结合备忘录,避免重复计算。
  • 使用哈希表存储字典,加快查询速度。

总结

LeetCode 139 和 140 是动态规划算法中的经典问题。DFS + 备忘录算法是一种通用的解决方法,而针对特定问题的优化技巧可以显著提高算法效率。通过深入理解这些算法,我们不仅提升了算法功底,更培养了举一反三的能力。

常见问题解答

  1. 为什么 DFS + 备忘录算法时间复杂度较高?

DFS + 备忘录算法在最坏情况下需要枚举所有可能的单词拆分方案,因此时间复杂度为 O(2^n),其中 n 是字符串长度。

  1. 滚动数组与普通数组有什么区别?

滚动数组只存储当前层的结果,当进入下一层时,将上一层的结果覆盖。这种方式减少了空间复杂度,因为我们不需要存储所有的中间结果。

  1. 为什么需要对字典按长度排序?

按长度排序的字典可以让我们快速判断一个单词是否是字符串的前缀,从而避免不必要的遍历。

  1. LeetCode 140 的优化技巧有哪些?

除了使用备忘录和哈希表外,LeetCode 140 的优化技巧还包括剪枝(跳过不可能的单词组合)和记忆化搜索(保存已经计算过的结果)。

  1. 动态规划算法在实际应用中有哪些优势?

动态规划算法擅长解决重叠子问题,因此在优化、搜索和组合问题中得到了广泛应用。例如,动态规划算法可以用于求解最长公共子序列、背包问题和旅行商问题。