返回

搭建知识之梯:深入剖析「剑指 Offer II 115. 重建序列」

后端

序幕:题意解析

「剑指 Offer II 115. 重建序列」是一道经典的拓扑排序题目,旨在考验您对图论知识的掌握程度。题目如下:

给定一个长度为 n 的整数数组 nums,其中每个元素表示一个节点,且满足以下条件:

  • 若 nums[i] > nums[j],则存在一条从节点 j 到节点 i 的有向边。
  • 每个元素均出现且仅出现一次。

请根据以上条件重建出图的拓扑顺序。如果有多个拓扑顺序,则返回其中任意一个。

为了帮助您更好地理解题目要求,我们不妨举一个简单的例子。假设 nums = [4, 1, 5, 2, 6, 3],则可以构建出如下的有向图:

1 → 2
↓   ↑
4 ← 3
↓   ↑
5 ← 6

在这个图中,从节点 1 到节点 6 存在一条拓扑顺序。同样,从节点 4 到节点 3 也存在一条拓扑顺序。题目要求您找到任意一条拓扑顺序,因此您需要返回 [1, 2, 3, 4, 5, 6] 或 [4, 3, 2, 1, 5, 6] 中的任意一个。

第一幕:拓扑排序的奥秘

拓扑排序是一种用于对有向无环图(DAG)进行排序的算法。DAG 是一种特殊的图,其中不存在环路。拓扑排序的目的是将 DAG 中的节点排列成一个线性序列,使得对于任意一对节点 u 和 v,如果 u 到 v 存在一条有向边,则 u 在序列中位于 v 之前。

拓扑排序的应用非常广泛,例如项目管理、任务调度和软件依赖关系分析等。在本文中,我们将重点讨论如何使用拓扑排序来解决「剑指 Offer II 115. 重建序列」这道题目。

第二幕:算法思路剖析

解决「剑指 Offer II 115. 重建序列」的关键在于将给定的整数数组 nums 视为一个 DAG 的节点集合,并将数组中元素之间的关系视为 DAG 中的边。然后,我们就可以使用拓扑排序算法对 DAG 进行排序,从而得到一个拓扑顺序。

拓扑排序的算法步骤如下:

  1. 初始化:

    • 将所有节点的入度(即指向该节点的有向边数)初始化为 0。
    • 将所有入度为 0 的节点放入一个队列中。
  2. 拓扑排序:

    • 从队列中取出一个节点。
    • 将该节点的所有出边指向的节点的入度减 1。
    • 如果某个节点的入度变为 0,则将其放入队列中。
  3. 重复步骤 2,直到队列为空。

拓扑排序的最终结果就是一个拓扑顺序。

第三幕:代码实现与实例解析

def topological_sort(nums):
  """
  对给定的整数数组 nums 进行拓扑排序。

  参数:
    nums: 一个长度为 n 的整数数组,其中每个元素表示一个节点。

  返回:
    一个拓扑顺序。
  """

  # 初始化入度数组和队列
  in_degree = [0] * len(nums)
  queue = []

  # 计算每个节点的入度
  for i in range(len(nums)):
    for j in range(len(nums)):
      if nums[i] > nums[j]:
        in_degree[i] += 1

  # 将所有入度为 0 的节点放入队列
  for i in range(len(nums)):
    if in_degree[i] == 0:
      queue.append(i)

  # 拓扑排序
  result = []
  while queue:
    # 取出队列中的一个节点
    node = queue.pop(0)

    # 将该节点加入结果中
    result.append(node)

    # 将该节点的所有出边指向的节点的入度减 1
    for i in range(len(nums)):
      if nums[node] > nums[i]:
        in_degree[i] -= 1

        # 如果某个节点的入度变为 0,则将其放入队列
        if in_degree[i] == 0:
          queue.append(i)

  return result


# 测试代码
nums = [4, 1, 5, 2, 6, 3]
result = topological_sort(nums)
print(result)

运行以上代码,即可得到一个拓扑顺序:[1, 2, 3, 4, 5, 6]。

尾声:算法的启示

通过对「剑指 Offer II 115. 重建序列」这道题目的深入解析,我们不仅掌握了一种解决拓扑排序问题的通用方法,还领悟到了算法设计中的一些重要思想。这些思想包括:

  • 抽象与建模: 将给定的问题抽象成一个图论问题,并使用拓扑排序算法来解决。
  • 分而治之: 将问题分解成更小的子问题,逐个解决,然后将子问题的解组合成总问题的解。
  • 贪心算法: 在每次选择时,做出局部最优的选择,期望得到全局最优的解。

这些思想在算法设计中非常重要,掌握这些思想将帮助您成为一名优秀的算法工程师。