返回

巧解LeetCode 38:外观数列的巧妙递归实现

前端

引言

LeetCode 38:外观数列是一道经典的编程题,它考验了递归和字符串操作的技巧。所谓外观数列,是一个从数字 1 开始的整数序列,其中每一项都是对前一项的。例如:

1 -> "1"
11 -> "11"
21 -> "12"
1211 -> "1112"
111221 -> "312211"

由此可见,外观数列的生成规则是:对于序列中的第 n 项,先第 n-1 项中各个数字出现的次数,再将这些描述串联起来。

递归实现

基于外观数列的生成规则,我们可以使用递归来巧妙地实现它。以下是 Python 中的递归实现:

def count_and_say(n):
  if n == 1:
    return "1"
  else:
    prev = count_and_say(n - 1)
    result = []
    count = 1
    for i in range(1, len(prev) + 1):
      if i < len(prev) and prev[i] == prev[i - 1]:
        count += 1
      else:
        result.append(str(count))
        result.append(prev[i - 1])
        count = 1
    return "".join(result)

在这个递归函数中,我们首先判断 n 是否为 1,如果是,则返回 "1"。否则,我们递归调用 count_and_say(n - 1) 获取前一项。然后,我们遍历前一项,统计每个数字出现的次数,并将这些次数和对应的数字添加到 result 列表中。最后,我们将 result 列表中的元素连接起来,得到外观数列的第 n 项。

时间复杂度

递归实现的时间复杂度为 O(n^2),因为对于外观数列的第 n 项,我们需要遍历前一项,而前一项的时间复杂度也是 O(n)。

动态规划

除了递归实现,还可以使用动态规划来解决这道题。动态规划的核心思想是将问题分解成子问题,并存储子问题的解,以避免重复计算。

def count_and_say_dp(n):
  dp = ["1"]
  for i in range(1, n):
    prev = dp[i - 1]
    result = []
    count = 1
    for j in range(1, len(prev) + 1):
      if j < len(prev) and prev[j] == prev[j - 1]:
        count += 1
      else:
        result.append(str(count))
        result.append(prev[j - 1])
        count = 1
    dp.append("".join(result))
  return dp[n - 1]

在动态规划实现中,我们使用 dp 列表存储外观数列的前 n 项。对于第 n 项,我们直接从 dp 列表中获取,避免了递归过程中的重复计算。因此,动态规划实现的时间复杂度为 O(n^2),与递归实现相同。

总结

LeetCode 38:外观数列是一道经典的编程题,它考验了递归和字符串操作的技巧。通过使用巧妙的递归或动态规划实现,我们可以高效地生成外观数列的第 n 项。掌握外观数列的生成规则,不仅可以帮助我们解决这道编程题,更能加深我们对字符串操作和递归算法的理解。