返回

从缓存到表:动态规划的简单入门

后端

从缓存到表:动态规划的简单入门

引论:算法与效率

在计算机科学领域,算法扮演着至关重要的角色。它是一系列步骤的集合,用于解决特定问题。算法的效率尤为关键,因为它决定了问题的求解时间和资源消耗。为了提升算法的效率,我们引入了一种名为“动态规划”的技术。

动态规划的初识:建立缓存表

动态规划的基本思想在于将问题的子问题结果存储在缓存表中,以便在需要时直接查表,避免重复计算。这种方法可以大大提高算法的效率。

案例一:斐波那契数列

斐波那契数列是一个经典的数学问题。它的定义如下:

F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2) (n ≥ 2)

若使用递归的方式来求解斐波那契数列,时间复杂度为指数级,非常低效。而采用动态规划的方法,则只需存储已经计算过的子问题结果,当再次遇到相同子问题时,直接查表即可。如此一来,时间复杂度将降至线性级别。

def fib(n, memo):
  """计算斐波那契数列的第n项。

  Args:
    n: 要计算的项数。
    memo: 一个缓存表,存储已经计算过的子问题结果。

  Returns:
    斐波那契数列的第n项。
  """

  # 检查是否存在缓存结果
  if n in memo:
    return memo[n]

  # 计算第n项并存储在缓存表中
  if n <= 1:
    result = n
  else:
    result = fib(n-1, memo) + fib(n-2, memo)
  memo[n] = result

  return result


def main():
  # 创建缓存表
  memo = {}

  # 计算并输出斐波那契数列的前10项
  for n in range(10):
    print(f"F({n}) = {fib(n, memo)}")


if __name__ == "__main__":
  main()

输出:

F(0) = 0
F(1) = 1
F(2) = 1
F(3) = 2
F(4) = 3
F(5) = 5
F(6) = 8
F(7) = 13
F(8) = 21
F(9) = 34

案例二:最长公共子序列

最长公共子序列(Longest Common Subsequence,LCS)问题是指,给定两个字符串,求出它们的共同子序列中长度最长的一个。

def lcs(s1, s2, memo):
  """计算两个字符串的最长公共子序列的长度。

  Args:
    s1: 第一个字符串。
    s2: 第二个字符串。
    memo: 一个缓存表,存储已经计算过的子问题结果。

  Returns:
    两个字符串的最长公共子序列的长度。
  """

  # 检查是否存在缓存结果
  key = (s1, s2)
  if key in memo:
    return memo[key]

  # 计算最长公共子序列的长度并存储在缓存表中
  if not s1 or not s2:
    result = 0
  elif s1[-1] == s2[-1]:
    result = lcs(s1[:-1], s2[:-1], memo) + 1
  else:
    result = max(lcs(s1[:-1], s2, memo), lcs(s1, s2[:-1], memo))
  memo[key] = result

  return result


def main():
  # 创建缓存表
  memo = {}

  # 计算并输出两个字符串的最长公共子序列的长度
  s1 = "ABCDGH"
  s2 = "AEDFHR"
  print(f"LCS({s1}, {s2}) = {lcs(s1, s2, memo)}")


if __name__ == "__main__":
  main()

输出:

LCS(ABCDGH, AEDFHR) = 3

结语:动态规划的强大力量

通过以上两个案例,我们了解了动态规划的基本思想和应用方法。动态规划的强大之处在于,它可以将一个复杂问题分解成更小的子问题,并通过存储子问题的结果来避免重复计算。这使得动态规划算法的时间复杂度大大降低,从而提高了算法的效率。