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 必须覆盖所有元素

    否则一定无法重建。

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

相关推荐
长安er5 小时前
LeetCode215/347/295 堆相关理论与题目
java·数据结构·算法·leetcode·
元亓亓亓5 小时前
LeetCode热题100--62. 不同路径--中等
算法·leetcode·职场和发展
小白菜又菜5 小时前
Leetcode 1925. Count Square Sum Triples
算法·leetcode
登山人在路上6 小时前
Nginx三种会话保持算法对比
算法·哈希算法·散列表
写代码的小球7 小时前
C++计算器(学生版)
c++·算法
AI科技星7 小时前
张祥前统一场论宇宙大统一方程的求导验证
服务器·人工智能·科技·线性代数·算法·生活
Fuly10247 小时前
大模型剪枝(Pruning)技术简介
算法·机器学习·剪枝
Xの哲學7 小时前
Linux网卡注册流程深度解析: 从硬件探测到网络栈
linux·服务器·网络·算法·边缘计算
bubiyoushang8887 小时前
二维地质模型的表面重力值和重力异常计算
算法
仙俊红8 小时前
LeetCode322零钱兑换
算法