【问题标题】:Time for 2 nodes to collide2个节点碰撞的时间
【发布时间】:2022-01-09 00:45:25
【问题描述】:

我们得到了N 节点的图。 (1-N),其中每个节点恰好有指向某个节点的1 指向边(该节点可以是同一个节点)。

我们需要回答类型为A, B查询,如果一个对象从A 开始,另一个对象从@ 开始,则询问两个对象碰撞所需的时间 987654326@。两个动作1 跳进1 秒。如果它们不可能发生冲突,时间将是-1

时间:从X -> 到Y:1 跳 = 1 秒。

约束

N, Q <= 10^5 (number of nodes, number of queries).

示例:对于给定的图表

   A -> B -> C -> D -> E
                  ^    |
                  K <- F

Query(A, E) : 3 seconds, as at time t = 3 secs they both will be on node D.
Query(C, D) : -1 seconds, as they will never collide.

回答每个问题的最佳方式是什么?

蛮力方法:时间 - O(Q * N)

使用二元提升技术改进的解决方案:时间 - O(Q * log(N))

private static int[] collisionTime(int N, int Q, int[] A, int[][] queries) {

    // ancestor matrix : creation time- O(n * log(n))
    int M = (int) (Math.ceil(Math.log10(N) / Math.log10(2))) + 1;
    int[][] ancestor = new int[N + 1][M];
    for(int i = 1; i <= N; i++) {
        ancestor[i][0] = A[i]; // 2^0-th ancestor. 
    }
    for(int j = 1; j < M; j++) {
        for(int i = 1; i <= N; i++) {
            ancestor[i][j] = ancestor[ancestor[i][j-1]][j-1];
        }
    }

    int[] answer = new int[Q];
    for(int i = 0; i < Q; i++) { 
        int u = queries[i][0];
        int v = queries[i][1];
        answer[i] = timeToCollide(u, v, ancestor);
    }

    return answer;
}

// using binary lifting: time- O(log(n))
private static int timeToCollide(int u, int v, int[][] ancestor) {
    int m = ancestor[0].length;

    // edge cases
    if(u == v)                              // already in collision state
        return 0;              
    if(ancestor[u][m-1] != ancestor[v][m-1]) // their top most ancestor is not the same means they cannot collide at all.
        return -1;

    int t = 0;
    for(int j = m - 1; j >=0; j--) {
        if(ancestor[u][j] != ancestor[v][j]) {
            u = ancestor[u][j];
            v = ancestor[v][j];
            t += Math.pow(2, j);
        }
    }
    return t + 1;
}

【问题讨论】:

  • 从图中,您可能有循环和“队列”大小,因此可以使用模数。不需要蛮力。
  • 我认为您可以在O(Q + N) 中进行操作。虽然不确定...
  • 这是我想到的一种方法。 预存储从任何顶点 u 到 v 之间的最短路径。我认为最多需要 O(n^2)。现在所有的查询都可以在 O(1) 内得到回答。
  • @tusharRawat 是的,这就是我一直在想的。我认为那将是O(N log N) 来构建它,然后O(Q log N) 来回答查询。所以总共O((N+Q) log N)
  • @AKSingh,我将使用二进制提升添加我的解决方案。问题不再可用,因此您现在无法访问它,但如果您愿意,我可以提供一些 4-5 个示例测试用例。

标签: algorithm math data-structures graph tree


【解决方案1】:
  1. 找到所有终端循环,并指定每个终端循环中的任意顶点作为循环根 (O(N))
  2. 对于每个顶点,记录其终端循环的长度、到进入终端循环的距离以及到终端循环根的距离 (O(N))。
  3. 切断每个循环根的传出链接。这会将图表变成森林。
  4. 对于每个查询,查找此林中两个查询节点的最近(最低)共同祖先。
  5. 从保存的有关每个查询节点和最低共同祖先的信息中,您可以计算出在恒定时间内发生碰撞的时间。

步骤(4),最低的共同祖先查询,是一个非常well-studied problem。最好的算法只需要线性处理时间并提供恒定的查询时间,导致这个问题总共需要 O(N + Q) 时间。

【讨论】:

  • 我一直在准备一个与此有很多相似之处的答案。但是,通过切断循环根的外链(步骤 3),您实际上会更改一些查询答案。我认为这仍然可以工作,因为当我对可能发生碰撞的所有可能方式进行分类时,循环上的任何碰撞都可以在有效的 O(1) 中确定。
  • 是的。这就是为什么你需要距离到循环和循环长度除了距离到循环根
  • 我一直在阅读有关 LCA 的 Wikipedia 文章和相关的 Heavy-Path Decomposition 文章,但他们似乎没有足够的信息来实现任何 O(n) 算法。他们还暗示其中一些“更难以实施”,这似乎是不祥之兆。对可以实施的描述有什么建议吗?
  • @RBarryYoung 由于不必按给定顺序处理查询,因此可以使用 Tarjan 的 LCA,简单快捷:en.wikipedia.org/wiki/… 对于按顺序处理,我会使用缩减通过此处描述的欧拉之旅进行范围最小查询:www-di.inf.puc-rio.br/~laber/lca-rmq.pdf 不过,那是很多代码。
  • 呵呵,我错过了 LCA 文章中的那个链接(可能是因为我没有理解在线/离线的含义)。我会看看它...谢谢你的链接。
【解决方案2】:

我相信以下方法在技术上可以达到O(N+Q) 的时间复杂度。

观察

子图:图不一定是连续的。所有图由一个或多个不相交的连续完整子图组成,含义:

  • 子图之间没有共享节点(“不相交”)
  • 子图中的所有节点都是连接的(“连续的”)
  • 没有连接不同子图的路径(“完整”)

我以后将这些称为图的子图或简称为“子图”。这些子图具有以下附加属性,这是它们的定义(上图)和图中节点类型的结果(它们都是只有一个出边/指针的“父指针节点”):

  • 所有此类子图中必须恰好有一个循环(因为循环是它们可以终止或关闭的唯一方式)
  • 循环可以是任意长度cycle.Len &gt;= 1
  • 此外,可能有任意数量的 (t &gt;= 0) 树在其根(基)处附加到循环
  • 所有节点要么在循环中,要么在其中一棵树中(树的根在循环中,但也算作树的一部分)

条款:

  • cycle Length:一个循环中的节点数。
  • cycle Base:循环中任意选择的节点,用于测量同一循环中两个节点之间的距离和距离,以及同一子图中的任意两个节点。
  • tree Base:连接到循环的其中一棵树的基节点或根节点。由于树基也是将其附加到循环的节点,因此树基节点被计为在循环中(并且也是其树的一部分)。
  • 距离:对于循环中的节点,这是从该节点到循环基的距离(跳数)(如果它循环基则为零)。对于树中的一个节点(不计算树基节点,算在循环中),这是该节点到树基节点的距离。

碰撞案例

琐碎的

在一个图中有许多可能的碰撞方式或“形式”,但我们可以预先确定两种微不足道的情况:

(A, B) Relation Collide? Collision Distance
same node Yes 0
different subgraphs No -1

很明显,如果 A 和 B 是同一个节点,那么它们在距离为零时很容易发生碰撞。同样,如果它们在两个不同的子图中,那么它们永远不会发生碰撞,因为子图之间没有连接。对于接下来的碰撞案例,我将假设这两个案例已经被过滤掉了:

  1. 假设 A 和 B 是不同的节点,并且
  2. 假设 A 和 B 在同一个子图中

不平凡的

下表涵盖了两个节点之间关系的所有其他重要情况。

(A, B) Relation Collide? Collision Distance Notes
same cycle No -1 nodes in cycle always stay the same distance apart
A in a tree & B in the cycle (or vice-versa) if they both arrive at A's treeBase at the same time -1 OR A.treeDist if B.cycleDist = (A.treeDist MOD cycle.Len)
A and B are in different trees if A and B's distance to their cycle.Base is equal MOD cycle.Len MAX(A.treeDist, B.treeDist) They meet when the farther one gets to the cycle (tree root)
A & B are in the same tree, but have different treeDist's If their treeDist's are equal MOD cycle.Len MAX(A.treeDist, B.treeDist) They meet when the farther one gets to the cycle (tree root/base)
A & B are in the same tree, and have the same treeDist's Yes At their lowest common ancestor (LCA) in the tree Have to search up the tree

上面多次应用的一个重要原则是循环中的两个不同节点永远不会发生冲突。这是因为当每个节点在循环中遵循其路径时,它们将始终保持相同的距离,一个节点的路径无法在循环中“赶上”另一个节点的路径。它们只能“碰撞”如果它们在同一个节点的循环中开始

这样做的后果是:

  1. 循环中的两个不同节点永远不会发生冲突。
  2. 树中的节点只能与循环中的节点发生碰撞,前提是它们到循环基的总距离相同,以循环长度取模(即除以循环长度后的余数)。
  3. 这也适用于不同树中的两个节点和同一树中的两个节点,但到它们的树基的距离不同。
  4. 在所有这些情况下(来自 #2 和 #3),当离其树基最远的节点到达循环(也是其树基)时,它们将发生冲突。这是因为循环中的节点不能相互“追赶”,因此一旦它们都在循环中,它们必须始终相同。因此,当更远的人最终到达循环时,它们总是会发生碰撞。

另一个重要的结果是,上面两个表中的每个案例,除了最后一个,都可以在O(1) 时间内回答,只需用如此容易确定的信息注释节点:

  1. 它们的基础节点(树或循环)
  2. 他们到那个基础节点的距离
  3. 它们所属的子图
  4. 子图的循环长度

在以O(1) 每个节点的时间(或O(N) 总时间)初始化图表时,这些都可以很容易地确定。

方法

节点

在节点最初加载到图形(MPDGraph 对象)后,我使用上面列出的附加信息对节点进行注释。这个过程(代码中称为“映射”)进行如下:

  1. 选择任意节点
  2. 跟随它的路径,直到它通过到达已经在它的路径中的节点或之前映射的节点而“终止”
  3. 如果#2 越过了它自己的路径,那么我们发现了一个新的循环。将我们穿越的节点指定为循环的基节点,并填写映射属性(循环、基节点、距离等)。一次展开我们的路径一个节点,填充每个节点并将其标记为 InCycle,因为我们返回路径直到我们再次到达基本节点。现在我们吃掉了我们的路径进入循环的树的基础,所以当我们回到路径中的前一个节点时,我们切换到将其标记为树节点,直到我们返回到路径中的第一个节点。
  4. 如果相反,#2 到达了一个已经映射的节点,那么我们很好地将我们的路径附加到该节点并将其树/循环、基础等信息复制到我们当前的节点。然后我们将返回路径,如 #3 所示,设置每个节点的映射属性,
  5. 如果有任何未映射的节点,请选择一个并转到 #2。

这一切都需要O(N) 时间。

查询

我们有一个方法(称为 MPDGraph.FindCollision),给定两个节点将应用上面两个碰撞案例表中的规则并返回结果。对于除了最后一个(同一树中的节点和相同距离)之外的其他情况,可以使用映射属性在O(1) 时间确定距离。

如果这两个节点在同一棵树中,并且与它们的树基的距离也相同,那么它们可以在它们和它们的公共 treeBase 节点之间的任何地方相遇。对于这种情况,FindCollision(A,B) 方法调用 findTreeDistance(A,B),其中:

  1. 如果它们是同一个节点,则返回零。
  2. 否则,它会检查先前计算的距离的缓存,以查看是否已经为这两个节点计算过。如果是,则返回该值。
  3. 否则,它调用findTreeDistance 传入当前两个节点的父节点以获取它们的距离,并将其加一。然后它将它添加到缓存中并返回值。

如果没有这种记忆(即缓存),这将平均需要大约 10 分钟。 O(log N) 用于此类型的每个查询。使用 memoization 很难计算,但我猜不会比 O(log log N) 差,但是对于比 N 大得多的 Q 计数,这将收敛到 O(1)

这使得查询处理时间复杂度介于O(Q log log N)O(Q) 之间,总时间介于O(N + Q(log log N))O(N + Q) 之间。

代码

public static int[] collisionTime(int N, int Q, int[] A, int[,] queries)
{
    // create the graph and fill-in the mapping attributes for all nodes
    var graph = new MPDGraph(A);
    graph.MapAllNodes();

    int[] answers = new int[queries.GetLength(0)];
    for (int i = 0; i < answers.Length; i++)
    {
        answers[i] = graph.FindCollision(queries[i, 0], queries[i, 1]);
    }

    return answers;
}

这利用了以下类,

MPDGraph 类:

// MPDG: Mono-Pointing, Directed Graph 
//  An MPDG is a directed graph where every node has exactly one out-edge.
//  (MPDG is my own term, I don't know the real name for these)
class MPDGraph
{
    public Node[] Nodes;
    Dictionary<(Node, Node), int> cachedDistances = new Dictionary<(Node, Node), int>();

    // constructor
    public MPDGraph(int[] Pointers)
    {
        Nodes = new Node[Pointers.Length];

        // first fill the array with objects
        for (int i = 0; i < Nodes.Length; i++) { Nodes[i] = new Node(i); }

        // apply their pointers
        for(int i = 0; i < Nodes.Length; i++) { Nodes[i].toNode = Nodes[Pointers[i]]; }
    }


    // map all of the nodes by filling their mapping properties
    public void MapAllNodes()
    {
        for(int i=0; i<Nodes.Length; i++)
        {
            if (!Nodes[i].isMapped)
                MapPath(Nodes[i], 1);
        }
    }

    // recursively map a path of unmapped nodes, starting from curr
    //  returns true if curr is in a cycle, false otherwise
    public Boolean MapPath(Node curr, int pathNo)
    {
        Boolean inCycle = false;
        curr.pathNo = pathNo;

        Node next = curr.toNode;

        if (next.IsInPath)
        {
            // we have found a new cycle
            Cycle Cycle = new Cycle(this, next, curr.pathNo - next.pathNo + 1);
            curr.Map(Cycle);
            return true;
        }
        else if (next.isMapped)
        {
            // we are joining an already partially mapped tree
            if (next.IsInCycle)
            {
                // next is a tree-Base, the top of our tree and also in the cycle
                curr.Map(next.Cycle, false, next, 1);
            }
            else
            {
                // next is a normal tree-node
                curr.Map(next.Cycle, false, next.BaseNode, next.Distance + 1);
            }
            return false;
        }
        else
        {
            // continue forward on the path, recurse to the next node
            inCycle = MapPath(next, pathNo+1);

            if (inCycle)
            {
                if (next.Cycle.Base == next || next.Distance == 0)
                {
                    //we have returned from the cycleBase, which is also a treeBase
                    // so, switch from Cycle to Tree
                    curr.Map(next.Cycle, false, next, 1);
                    return false;
                }
                else
                {
                    // still in the cycle
                    curr.Map(next.Cycle);
                }
            }
            else
            {
                //returned in tree
                curr.Map(next.Cycle, false, next.BaseNode, next.Distance + 1);
            }

            return inCycle;
        }

    }


    // Given two starting nodes, determine how many steps it takes until their
    //  paths collide. Returns -1 if they will never collide.
    public int FindCollision(int index1, int index2)
    {
        Node node1 = Nodes[index1];
        Node node2 = Nodes[index2];

        // eliminate trivial cases
        if (node1.Cycle != node2.Cycle)
            return -1;      // cant collide if they're in different subgraphs
        else if (node1 == node2)
            return 0;       // if they're the same node, then distance = 0
        else if (node1.IsInCycle && node2.IsInCycle)
            return -1;      // different nodes in a cycle never collide
        
        else
        {  // they're both in the same subgraph, use math to tell if they collide

            // get their distances to the cycle base
            int dist1 = node1.Distance + (node1.IsInCycle ? 0 : node1.BaseNode.Distance);
            int dist2 = node2.Distance + (node2.IsInCycle ? 0 : node2.BaseNode.Distance);
            int cycleLen = node1.Cycle.Length;

            // use math:  modulo(cycle length)
            if ((dist1 % cycleLen) != (dist2 % cycleLen))
            {
                return -1;      // incompatible distances: cannot possibly collide
            }
            else
            {
                // they must collide somewhere, figure out how far that is
                if (node1.IsInCycle || node2.IsInCycle)
                {
                    // if one is in the cycle, they will collide when
                    // the other one reaches the cycle (it's treeBase)
                    return (!node1.IsInCycle ? node1.Distance : node2.Distance);
                }
                else if (node1.BaseNode != node2.BaseNode)
                {
                    // They are in different trees:  they will collide at
                    //  the treeBase of the node that is farther
                    return Math.Max(node1.Distance, node2.Distance);
                }
                else
                {
                    // They are in the same tree:
                    if (node1.Distance != node2.Distance)
                    {
                        //if they are in the same tree, but have different distances
                        //  to the treeBase, then they will collide at the treeBase
                        //  when the farther one arrives at the treeBase
                        return Math.Max(node1.Distance, node2.Distance);
                    }
                    else
                    {
                        //  the hard case, have to walk down their paths
                        //  to find their LCA (Lowest Common Ancestor)
                        return findTreeDistance(node1, node2);
                    }
                }
            }
        }
    }


    int findTreeDistance(Node node1, Node node2)
    {
        if (node1 == node2) return 0;

        // normalize their order
        if (node1.index > node2.index)
        {
            var tmp = node1;
            node1 = node2;
            node2 = tmp;
        }

        // check the cache
        int dist;
        if (cachedDistances.ContainsKey((node1,node2)))
        {
            dist = cachedDistances[(node1, node2)];
        }
        else
        {
            // keep recursing to find where they meet
            dist = findTreeDistance(node1.toNode, node2.toNode) + 1;
            // cache this new distance
            cachedDistances.Add((node1, node2), dist);
        }
        return dist;
    }
}

节点类:

// Represents a node in the MPDG (Mono-Pointing Directed Graph) with the constraint
//  that all nodes have exactly one out-edge ("toNode").
class Node
{
    // Primary properties (from input)
    public int index { get; set; }      // this node's unique index (to the original array)
    public Node toNode { get; set; }    // what our single out-edge is pointing to


    public Node(int Index_) { this.index = Index_; }


    // Mapping properties
    // (these must be filled-in to finish mapping the node)

    //  The unique cycle of this node's subgraph (all MPDG-subgraphs have exactly one)
    public Cycle Cycle;

    //  Every node is either in their subgraphs cycle or in one of the inverted
    // trees whose apex (base) is attached to it.  Only valid when BaseNode is set.
    // (tree base nodes are counted as being in the cycle)
    public Boolean IsInCycle = false;

    // The base node of the tree or cycle that this node is in.
    //  If (IsInCycle) then it points to this cycle's base node (cycleBase)
    //  Else it points to base node of this node's tree (treeBase)
    public Node BaseNode;

    //  The distance (hops) from this node to the BaseNode
    public int Distance = -1;    // -1 if not yet mapped

    // Total distance from this node to the cycleBase
    public int TotalDistance { get { return Distance + (IsInCycle ? 0 : BaseNode.Distance); } }


    // housekeeping for mapping nodes
    public int pathNo = -1;          // order in our working path

    // Derived (implicit) properties
    public Boolean isMapped { get { return Cycle != null; } }
    public Boolean IsInPath { get { return (pathNo >= 0); } }


    public void Map(Cycle Cycle, bool InCycle = true, Node BaseNode = null, int distance_ = -1)
    {
        this.Cycle = Cycle; 
        IsInCycle = InCycle;

        if (InCycle)
        {
            this.BaseNode = Cycle.Base;
            Distance = (Cycle.Length + (Cycle.Base.pathNo - pathNo)) % Cycle.Length;
        }
        else
        {
            this.BaseNode = BaseNode;
            Distance = distance_;
        }

        pathNo = -1;    // clean-up the path once we're done
    }
}

自行车类:

// represents the cycle of a unique MPDG-subgraph
//  (should have one of these for each subgraph)
class Cycle
{
    public MPDGraph Graph; // the MPDG that contains this cycle
    public Node Base;      // the base node of a subgraph's cycle
    public int Length;     // the length of this cycle

    public Cycle(MPDGraph graph_, Node base_, int length_)
    {
        Graph = graph_;
        Base = base_;
        Length = length_;
    }
}

性能测量:

Node Count Build & Map Graph
mean microsecs
Question Count All Questions
mean microsecs
Question mean
microseconds
Total mean
microseconds
50 0.9 1225 26 0.0212 26.9
500 10.1 124750 2267 0.0182 2277.1
1000 23.4 499500 8720 0.0175 8743.4
5000 159.6 12497500 229000 0.0183 229159.6
10000 345.3 49995000 793212 0.0159 793557.3

【讨论】:

  • 注意:稍后我将添加更多解释。同时让我知道任何问题。
  • 我认为找到一个 O(QN) 案例并不难——两条长路径到合并点,然后是一条到终端循环的路径。针对第二条长路径的每个节点查询第一条长路径的初始节点。对距离缓存的每次检查都会有一个唯一的键。如果你想要 O(N+Q),你需要使用一个好的 LCA 算法
  • @MattTimmermans 它在搜索碰撞点时记住匹配对的路径。这可以证明是O(N+Q),因为当 Q 接近无穷大时,每个 Q 的增量复杂度都接近 O(1)。这就是为什么对于较大的 N 和 Q,单个问题的时间实际上 下降,即使平均路径应该更长。
  • 我已经尝试了几个病理案例,无论如何,随着Q变大,它接近O(N + Q)。这就是为什么我说它技术上 O(N+Q)。虽然我同意,但使用 LCA 算法会更好。
  • OTOH,使用我正在使用的决策树,可以在 O(1) 中回答随机图的 2/3 随机查询,而无需记忆。
【解决方案3】:

冲突只可能发生在有超过 1 个链接的节点上。您的示例中的节点 D。

我们称这些节点为“崩溃站点”

因此,您可以将图表修剪为仅崩溃站点节点。导致崩溃站点节点的节点成为崩溃站点节点的属性。

你的例子是这样的:

D : { A,B,C }, { E,F,K }

只有当起始节点位于同一崩溃站点节点的两个不同属性列表上时,才会发生碰撞。

一旦您确定会发生崩溃,您就可以检查两个起始节点与崩溃现场的距离是否相同。

算法:

Prune graph to crash site nodes
LOOP over questions
Get 2 start nodes
LOOP over crash sites
   IF start nodes on two different attributes of crash site
        IF start nodes are equi-distant from crash site
             report crash time
             BREAK from crash site loop

这是一个随机生成的图,有 50 个节点,其中每个节点都有一条出边连接到随机选择的另一个节点

碰撞地点是

5 7 8 9 10 11 18 19 23 25 31 33 36 37 39

所以算法最多只需要循环 15 个节点,而不是 50 个。

“两个特定节点是否发生碰撞?”问题的答案几乎总是“不”。这种方式有点无聊。所以让我们问一个稍微不同的问题:“对于一个特定的图,哪些节点对会导致冲突?”这需要相同的算法(应用于每对节点),但总是会产生有趣的答案。

对于这张图

我得到这个答案

0 and 29 collide at 41
1 and 5 collide at 40
2 and 23 collide at 13
8 and 16 collide at 34
8 and 22 collide at 34
8 and 39 collide at 34
9 and 30 collide at 37
10 and 31 collide at 25
11 and 47 collide at 23
12 and 28 collide at 25
12 and 35 collide at 25
12 and 49 collide at 25
13 and 38 collide at 27
14 and 44 collide at 1
15 and 17 collide at 0
15 and 18 collide at 0
15 and 37 collide at 0
16 and 22 collide at 34
16 and 39 collide at 34
17 and 18 collide at 0
17 and 37 collide at 0
18 and 37 collide at 0
20 and 26 collide at 9
20 and 42 collide at 9
20 and 43 collide at 9
21 and 45 collide at 24
22 and 39 collide at 34
25 and 34 collide at 3
26 and 42 collide at 9
26 and 43 collide at 9
28 and 35 collide at 25
28 and 49 collide at 25
32 and 48 collide at 34
33 and 36 collide at 7
35 and 49 collide at 25
42 and 43 collide at 9

一些计时结果

Node Count Crash Sites
millisecs
Question Count Question mean
microsecs
50 0.4 1225 0.02
500 50 124750 0.02
5000 5500 ~12M 0.02
10000 30000 ~50M 0.03
30000 181000 ~450M 0.6

注意事项:

  • 一个问题的平均时间是检查每对可能的节点是否可能发生冲突的平均值。
  • 回答一个问题非常快,对于中等大小的图(
  • 对于中等大小的图表(> 5,000 个节点),设置崩溃站点及其支流会变得很慢。只有在要问很多问题时才值得这样做。

此代码可在https://github.com/JamesBremner/PathFinder获得

【讨论】:

  • 那么,如果我正确阅读了您的计时结果,您的问题列表实际上包含了所有可能的不同问题,对吗?
  • @RBarryYoung 是的。
  • OP 的“二元提升”技术在最坏的情况下更快。
  • 另外,如果崩溃点在一个终止循环上,那么两个节点即使距离不同也可能发生碰撞,只要差值是循环长度的倍数。
  • 也许吧。然而,最坏的情况极为罕见。所以问题是,您的常规病例方法有多快。我没有看到任何其他方法来回答这个问题的时间报告。
猜你喜欢
  • 1970-01-01
  • 2020-05-24
  • 2015-01-21
  • 1970-01-01
  • 2018-09-10
  • 2018-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多