leetcode
leetcode 401 ~ 450
序列重建

序列重建

难度:

标签:

题目描述

代码结果

运行时间: 82 ms, 内存: 19.6 MB


/*
 * Problem: Sequence Reconstruction
 * Given an original sequence org and a list of sequences seqs, write a method to determine if org can be uniquely reconstructed from the sequences in seqs. The original sequence org is a permutation of the integers from 1 to n, with 1 ≤ n ≤ 10^4. Reconstruction means building the shortest common supersequence of the sequences in seqs (i.e., a sequence that has all sequences in seqs as subsequences) and it should be unique.
 * 
 * Approach:
 * 1. Use a graph to represent dependencies between elements.
 * 2. Use a topological sort to determine if there's a unique way to reconstruct the sequence.
 */
 
import java.util.*;
import java.util.stream.*;
 
public class SequenceReconstruction {
    public boolean sequenceReconstruction(int[] org, List<List<Integer>> seqs) {
        if (org.length == 0 || seqs.size() == 0) return false;
 
        Map<Integer, Set<Integer>> graph = Arrays.stream(org).boxed().collect(Collectors.toMap(k -> k, v -> new HashSet<>()));
        Map<Integer, Integer> inDegree = Arrays.stream(org).boxed().collect(Collectors.toMap(k -> k, v -> 0));
 
        int count = seqs.stream().mapToInt(List::size).sum();
        if (count < org.length) return false;
 
        for (List<Integer> seq : seqs) {
            for (int i = 0; i < seq.size(); i++) {
                if (!graph.containsKey(seq.get(i))) return false;
                if (i > 0) {
                    int prev = seq.get(i - 1);
                    int curr = seq.get(i);
                    if (graph.get(prev).add(curr)) {
                        inDegree.put(curr, inDegree.get(curr) + 1);
                    }
                }
            }
        }
 
        Queue<Integer> queue = inDegree.entrySet().stream()
                .filter(entry -> entry.getValue() == 0)
                .map(Map.Entry::getKey)
                .collect(Collectors.toCollection(LinkedList::new));
 
        int index = 0;
        while (!queue.isEmpty()) {
            if (queue.size() > 1) return false;
            int curr = queue.poll();
            if (org[index] != curr) return false;
            index++;
            for (int next : graph.get(curr)) {
                inDegree.put(next, inDegree.get(next) - 1);
                if (inDegree.get(next) == 0) {
                    queue.add(next);
                }
            }
        }
 
        return index == org.length;
    }
}
 

解释

方法:

这个题解使用了拓扑排序的思路。首先根据给定的序列构建有向图,然后对图进行拓扑排序,判断拓扑排序的结果是否与给定的 nums 数组相同。在构建有向图时,使用 nexts 数组记录每个节点的后继节点,使用 inDegrees 数组记录每个节点的入度。在拓扑排序时,使用队列 q 存储入度为 0 的节点,每次从队列中取出一个节点,将其与 nums 数组中的元素进行比较,然后将其所有后继节点的入度减 1,如果入度变为 0,则将后继节点加入队列。如果在排序过程中出现了与 nums 数组不一致的情况,或者在某一轮中入度变为 0 的节点数量大于 1,则说明给定的序列无法唯一确定一个超序列,返回 False。最后,如果拓扑排序成功完成,且 nums 数组中的所有元素都被访问到,则说明可以重建出唯一的超序列,返回 True。

时间复杂度:

O(n+m)

空间复杂度:

O(n)

代码细节讲解

🦆
在构建有向图时,为何需要使用两个数组nexts和inDegrees分别记录后继节点和节点入度?它们在算法中分别承担什么角色?
在拓扑排序中,nexts数组用于存储图中每个节点的所有后继节点,这使得每次处理完一个节点后,可以迅速找到所有由此节点直接指向的后继节点,并更新这些后继节点的状态(例如减少其入度)。而inDegrees数组用于记录每个节点的入度数,即指向该节点的边的数量。入度为0的节点表示没有任何前置条件,可以立即处理。这两个数组共同支持了拓扑排序的实现:通过inDegrees来确定处理顺序,通过nexts来更新相关节点的状态。
🦆
题解中提到,如果初始时入度为0的节点数量大于1,则无法唯一确定超序列。为什么多个入度为0的节点会导致无法唯一确定超序列?
如果初始时入度为0的节点数量大于1,这意味着有多个节点可以作为序列的起始点,从而可能形成不同的序列排列。在序列重建的上下文中,目标是确认是否可以通过给定的序列片段唯一地重建一个超序列。如果存在多个可能的起始节点,这表明给定的序列片段没有足够的信息来确定一个唯一的序列,因此算法会返回False,指出无法唯一确定一个超序列。
🦆
当拓扑排序中的当前节点与nums数组中的元素不一致时,为何直接判断无法重建超序列?是否存在其他可能的情况允许继续拓扑排序?
拓扑排序的目的是确定节点的唯一线性顺序。在序列重建问题中,如果拓扑排序过程中的当前节点与预期的nums数组中的元素不一致,这意味着重建的序列与给定的目标序列nums不匹配。由于我们的目标是验证是否可以根据给定的序列片段精确地重建nums,任何不一致都会直接导致重建失败。不存在其他可能的情况允许继续拓扑排序,因为序列已经偏离了唯一的目标序列,继续排序不会恢复其与nums的一致性。
🦆
在处理拓扑排序的过程中,如果在某一轮中入度变为0的节点数量大于1,题解选择返回False。这种情况下,是否还有其他策略可以继续尝试重建超序列?
在严格的序列重建问题中,如果在某一轮中入度变为0的节点数量大于1,这意味着序列的下一步有多个可能的选择,从而无法唯一确定超序列。通常情况下,这直接导致返回False。如果问题允许多个合法序列,可以考虑所有可能的路径,并尝试每一条路径是否能最终重建为唯一序列。然而,在本题的目的是验证是否可以从给定的序列片段唯一地重建超序列,因此没有其他策略可以继续尝试重建超序列,而只能判断为无法重建。

相关问题

课程表 II

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai必须 先选修 bi

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1]

返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组

 

示例 1:

输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:

输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]

示例 3:

输入:numCourses = 1, prerequisites = []
输出:[0]

 

提示:
  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= numCourses * (numCourses - 1)
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • ai != bi
  • 所有[ai, bi] 互不相同