LeetCode 444 - 序列重建


文章目录

摘要

这道题是典型的"拓扑排序 + 唯一性判断"的组合题。我们不仅要判断能否从若干子序列中重建原序列,还要验证这个重建序列是不是唯一的。虽然听起来有点抽象,但一旦从"图论 + 顺序约束"的角度理解,整个思路就会清晰很多。

这题在实际工程里面其实很有应用价值,比如:

  • 根据操作日志恢复真实执行顺序
  • 事件流中根据局部约束恢复唯一事件链路
  • 构建依赖图时判断有没有唯一确定的执行顺序

文章中我会给出完整的 Swift 解法,并在 Demo 中直接运行。

描述

题目给你:

  • 一个整数数组 nums:这是你要恢复的最终序列;
  • 一个二维数组 sequences:每一条序列都只给出部分的顺序关系。

目标是判断:
这些局部序列能否唯一地拼出 nums

比如:

txt 复制代码
nums = [1,2,3]
sequences = [[1,2], [1,3]]

这时候顺序可能是 1 -> 2 -> 31 -> 3 -> 2

并不是唯一的,因此返回 false。

而:

txt 复制代码
nums = [1,2,3]
sequences = [[1,2], [2,3]]

局部约束拼起来得到的序列只能是 [1,2,3]

这是唯一一种顺序,因此返回 true。

题解答案

最关键的是两点:

  1. 拓扑排序构图:

    根据 sequences 中的相邻关系生成有向图,并记录入度。

  2. 唯一性判断:

    在拓扑排序过程中:
    如果某一时刻队列中有超过 1 个可选节点,说明排序结果不唯一 → 直接 false。

最终如果唯一顺序和 nums 完全一致,返回 true,否则 false。

题解代码分析

下面这段代码可以直接在 Swift Playground 或 Xcode 中跑起来。

swift 复制代码
import Foundation

class Solution {
    func sequenceReconstruction(_ nums: [Int], _ sequences: [[Int]]) -> Bool {
        let n = nums.count
        if n == 0 { return false }
        
        // 构建图
        var graph = Array(repeating: [Int](), count: n + 1)
        var indegree = Array(repeating: 0, count: n + 1)
        var exists = Array(repeating: false, count: n + 1)
        
        // 标记序列中出现过的元素
        for seq in sequences {
            for num in seq {
                if num < 1 || num > n { return false }
                exists[num] = true
            }
        }
        
        // 如果 sequences 中没有包含全部元素,肯定无法重建
        for i in 1...n {
            if !exists[i] { return false }
        }
        
        // 建图与入度
        for seq in sequences {
            if seq.count < 2 { continue }
            
            for i in 0..<seq.count - 1 {
                let from = seq[i]
                let to = seq[i + 1]
                graph[from].append(to)
                indegree[to] += 1
            }
        }
        
        // BFS 拓扑排序(需要保证唯一性)
        var queue = [Int]()
        
        // 入度为 0 的节点加入队列
        for i in 1...n {
            if indegree[i] == 0 {
                queue.append(i)
            }
        }
        
        var index = 0
        
        while !queue.isEmpty {
            // 如果当前队列中超过 1 个节点 → 不唯一
            if queue.count > 1 { return false }
            
            let cur = queue.removeFirst()
            
            // 顺序必须与 nums 一致
            if nums[index] != cur { return false }
            index += 1
            
            // 邻接节点入度减一
            for next in graph[cur] {
                indegree[next] -= 1
                if indegree[next] == 0 {
                    queue.append(next)
                }
            }
        }
        
        return index == n
    }
}


// MARK: - Demo 测试代码
func runDemo() {
    let sol = Solution()
    
    let nums1 = [1,2,3]
    let seqs1 = [[1,2],[1,3]]
    print("示例 1:", sol.sequenceReconstruction(nums1, seqs1))
    
    let nums2 = [1,2,3]
    let seqs2 = [[1,2],[2,3]]
    print("示例 2:", sol.sequenceReconstruction(nums2, seqs2))
    
    let nums3 = [1]
    let seqs3 = [[1]]
    print("示例 3:", sol.sequenceReconstruction(nums3, seqs3))
}

runDemo()

示例测试及结果

下面按照 Demo 的输出说明结果:

示例 1

txt 复制代码
nums = [1,2,3]
sequences = [[1,2], [1,3]]

输出:

txt 复制代码
false

原因:

1 之后可以接 2 或 3,不唯一。

示例 2

txt 复制代码
nums = [1,2,3]
sequences = [[1,2], [2,3]]

输出:

txt 复制代码
true

所有局部关系唯一指向 [1,2,3]

示例 3

txt 复制代码
nums = [1]
sequences = [[1]]

输出:

txt 复制代码
true

就一个数,当然唯一。

与实际场景结合

这个问题在真实开发中其实非常有意义。以下举几个常见场景:

1. 分布式系统中的事件顺序恢复

多个日志源分别记录部分事件顺序,比如日志 A 记录 1 -> 3,日志 B 记录 3 -> 4

你需要判断最终的事件顺序是否唯一,不然就无法可靠恢复系统状态。

2. 构建任务依赖图

CI/CD 或构建系统里,任务之间有依赖关系。

某些工具会根据依赖关系判断构建顺序是否唯一,避免潜在竞态。

3. 系统执行轨迹分析

调试复杂业务时,系统可能会在不同模块留下部分执行顺序。

你能否从这些局部顺序还原唯一执行轨迹?

这题就是最小化版本。

4. 版本控制系统中的操作序列解析

有些操作具有前后依赖,系统需要判断提交顺序是否唯一。

这类场景都是"根据局部顺序重建全局顺序"的典型需求。

时间复杂度

  • 建图遍历所有 sequences:
    O(total length of sequences)
  • 拓扑排序:
    O(n + edges)

整体为:

O(n + sum(sequences.length))

空间复杂度

  • 图结构:O(n + edges)
  • 入度数组、存在标记:O(n)
  • 队列:O(n)

整体:

O(n + edges)

总结

这道题表面是拓扑排序,但核心在:

  1. 拓扑排序过程中必须始终保持队列只有 1 个节点

    这说明顺序唯一。

  2. 输出顺序必须严格等于 nums

    防止 sequences 给的是有效拓扑,但不是你想要的序列。

  3. sequences 必须覆盖所有元素

    否则一定无法重建。

如果把拓扑排序理解透彻,这题就是很标准的"唯一拓扑排序"问题,是图论中非常有代表性的场景。

相关推荐
lsylalalala13 分钟前
常见的排序算法1
数据结构·算法·排序算法
想吃火锅10051 小时前
【leetcode】54. 螺旋矩阵
算法·leetcode·矩阵
想吃火锅10051 小时前
【leetcode】300. 最长递增子序列
算法·leetcode·职场和发展
imaol11 小时前
数据结构---队列
java·数据结构·算法
数模竞赛Paid answer1 小时前
2026年电工杯数学建模A题绿电直连型电氢氨园区优化运行求解全过程论文及程序
算法·数学建模·电工杯
疯狂打码的少年1 小时前
【数据结构】串的模式匹配:KMP算法(重点)
数据结构·笔记·算法
晚风醉蝶2 小时前
第零篇-算法全景图
算法
疯狂打码的少年3 小时前
【数据结构】树的基本概念与二叉树定义
java·数据结构·笔记·算法
webmote333 小时前
用 NVIDIA Nemotron 3 Super + .NET 构建有记忆的多轮对话
后端·算法
Keven_113 小时前
算法札记:区分稀疏图与稠密图在经验上的数学计算差异
算法·稠密图·稀疏图