面试:删除链表中的循环 - Java

发布于 2024-10-31 06:02:06 字数 373 浏览 1 评论 0原文

我在面试中被问到这个问题:“如何检测链表中的循环?”,我解决了这个问题,但面试官立即问我如何删除链表中的循环。我摸索着。

那么关于如何解决这个问题的任何指示可能是伪代码或方法定义?

我对 Java 很熟悉,所以我在 java 下标记了这个问题。

例如这个链表有一个循环

 0--->1---->2---->3---->4---->5---->6
                  ▲                 |
                  |                 ▼
                 11<—-22<—-12<—-9<—-8

I was asked this question in interview: "How to detect the loop in linked list?", I solved this but immediately the interviewer asked me how do I remove the loop in a linked list. I fumbled.

So any pointers on how to solve this, may be pseudo code, or method definition?

I'm comfortable with Java so I have tagged this question under java.

For Instance this linked list has a loop

 0--->1---->2---->3---->4---->5---->6
                  ▲                 |
                  |                 ▼
                 11<—-22<—-12<—-9<—-8

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

撧情箌佬 2024-11-07 06:02:06

这个问题有两个部分:

  1. 检测列表中是否存在循环
  2. 识别循环的开始

一旦知道循环开始的位置,就很容易识别列表中的最后一个元素,因为它是列表中紧随循环的开始,最终指向循环的开始。然后,将此元素的下一个指针/引用设置为 null 来纠正循环链接列表(不是循环链接列表,循环链接列表是最后一个元素指向第一个元素的位置 - 这将是一个循环列表的具体实例)。

  1. Floyd 的循环检测算法,也称为龟兔算法,因为它涉及使用以不同速度移动的两个指针/参考是检测周期的一种方法。如果存在循环,则两个指针(例如 p1p2)将在有限数量的步骤后最终指向同一个元素。有趣的是,可以证明它们相遇的元素循环开始的距离与开始的距离相同(继续以相同的向前方向遍历列表)循环的位置是列表的头部。也就是说,如果列表的线性部分有 k 个元素,则两个指针将在长度为 m 的循环内于点 mk 处相遇从循环的开始或 k 元素到循环的“结束”(当然,这是一个循环,因此它没有“结束” - 它只是再次“开始”)。这为我们提供了一种找到循环起点的方法:

  2. 一旦检测到循环,让p2保持指向上述步骤的循环终止但重置的元素>p1 以便它指向列表的头部。现在,将每个指针一次移动一个元素。由于 p2 在循环内部开始,因此它将继续循环。经过 k 步(等于循环起点到列表头部的距离)后,p1p2 将再次相遇。这将为您提供循环开始的引用。

  3. 现在可以轻松设置p1(或p2)以指向开始循环的元素并遍历循环直到p1结束up 指向起始元素。此时 p1 正在引用“最后一个”元素列表,并且它的下一个指针可以设置为 null


下面是一些快速而肮脏的 Java 代码,假设有一个 Node 的链接列表,其中 Node 具有 next 引用。这可以优化,但它应该给你基本的想法:

Node slow, fast, start;
fast = slow = head;

//PART I - Detect if a loop exists
while (true)
{
    // fast will always fall off the end of the list if it is linear
    if (fast == null || fast.next == null)
    {
        // no loop
        return;
    }
    else if (fast == slow || fast.next == slow)
    {
        // detected a loop
        break;
    }
    else
    {
        fast = fast.next.next; // move 2 nodes at at time
        slow = slow.next; // move 1 node at a time
    }
}

//PART II - Identify the node that is the start of the loop
fast = head; //reset one of the references to head of list

//until both the references are one short of the common element which is the start of the loop
while(fast.next != slow.next) 
{
    fast = fast.next;
    slow = slow.next;
}

start = fast.next;

//PART III - Eliminate the loop by setting the 'next' pointer 
//of the last element to null
fast = start;
while(fast.next != start)
{
    fast = fast.next;
}

fast.next = null; //break the loop

这个解释可能有助于解释第二部分背后的原因:

假设循环长度为M,
和其余部分的长度
链表是L。我们来算一下
在循环中处于什么位置时
t1/t2 第一次见面?

定义循环中的第一个节点是
位置 0,按照我们的链接
位置为 1、2、...,直至 M-1。 (
当我们走在循环中时,我们当前的
位置是 (walk_length) mod M,
对吗?)假设 t1/t2 第一次相遇于
位置 p,那么他们的旅行时间是
同样,(L+k1*M+p)/v = (L+k2*M+p)/2v
对于一些k1

因此得出的结论是,如果 t1 从
p, t2 从 head 开始移动到
同样的速度,然后受赠者会面
在位置 0 处,第一个节点
循环。量子ED。

更多参考:

There are two parts to this problem:

  1. Detect if there is a loop in the list
  2. Identify the start of the loop

Once you know where the loop starts, it's easy to identify the last element in the list since it's the element in the list following the start of the loop that ends up pointing back to the start of the loop. It is then trivial to set the next pointer/reference of this element to null to correct the cyclic link list (not circular linked list which is where the last elements points back to the first - this would be a specific instance of cyclic lists).

  1. Floyd's cycle detect algorithm, also called the tortoise and hare algorithm as it involves using two pointers/references that move at different speeds, is one way of detecting the cycle. If there is a cycle, the two pointers (say p1 and p2) will end up pointing to the same element after a finite number of steps. Interestingly, it can be proved that the element at which they meet will be the same distance to the start of the loop (continuing to traverse the list in the same, forward direction) as the start of the loop is to the head of the list. That is, if the linear part of the list has k elements, the two pointers will meet inside the loop of length m at a point m-k from the start of the loop or k elements to the 'end' of the loop (of course, it's a loop so it has no 'end' - it's just the 'start' once again). And that gives us a way to find the start of the loop:

  2. Once a cycle has been detected, let p2 remain pointing to the element where the loop for the step above terminated but reset p1 so that it's pointing back to the head of the list. Now, move each pointer one element at a time. Since p2 began inside the loop, it will continue looping. After k steps (equal to the distance of the start of the loop from the head of the list), p1 and p2 will meet again. This will give you a reference to the start of the loop.

  3. It is now easy to set p1 (or p2) to point to the element starting the loop and traverse the loop until p1 ends up pointing back to the starting element. At this point p1 is referencing the 'last' element list and it's next pointer can be set to null.


Here's some quick and dirty Java code assuming a linked list of Nodes where a Node has a next reference. This could be optimized but it should give you the basic idea:

Node slow, fast, start;
fast = slow = head;

//PART I - Detect if a loop exists
while (true)
{
    // fast will always fall off the end of the list if it is linear
    if (fast == null || fast.next == null)
    {
        // no loop
        return;
    }
    else if (fast == slow || fast.next == slow)
    {
        // detected a loop
        break;
    }
    else
    {
        fast = fast.next.next; // move 2 nodes at at time
        slow = slow.next; // move 1 node at a time
    }
}

//PART II - Identify the node that is the start of the loop
fast = head; //reset one of the references to head of list

//until both the references are one short of the common element which is the start of the loop
while(fast.next != slow.next) 
{
    fast = fast.next;
    slow = slow.next;
}

start = fast.next;

//PART III - Eliminate the loop by setting the 'next' pointer 
//of the last element to null
fast = start;
while(fast.next != start)
{
    fast = fast.next;
}

fast.next = null; //break the loop

This explanation might help the why behind Part II:

Assume the length of the cycle is M,
and the length of the rest of the
linked list is L. Let's figure out
what is the position in the cycle when
t1/t2 first meet?

Define the first node in the cycle is
position 0, following the links we
have position 1, 2,..., up to M-1. (
when we walk in the cycle, our current
position is (walk_length) mod M,
right?) Suppose t1/t2 first meet at
position p, then their travel time are
the same, (L+k1*M+p)/v = (L+k2*M+p)/2v
for some k1

So it concludes that if t1 start from
p, t2 start from head and move at the
same speed, then will grantee to meet
at position 0, the first node of the
cycle. QED.

More references:

千纸鹤带着心事 2024-11-07 06:02:06

解决方案 1 - 由职业杯和《破解编码面试》一书提供

public static LinkedListNode findStartOfLoop(LinkedListNode head) {
    LinkedListNode n1 = head;
    LinkedListNode n2 = head; 

    // find meeting point using Tortoise and Hare algorithm
    // this is just Floyd's cycle detection algorithm
    while (n2.next != null) { 
        n1 = n1.next; 
        n2 = n2.next.next; 
        if (n1 == n2) { 
            break; 
        }
    }

    // Error check - there is no meeting point, and therefore no loop
    if (n2.next == null) {
        return null;
    }

    /* Move n1 to Head. Keep n2 at Meeting Point.  Each are k steps
    /* from the Loop Start. If they move at the same pace, they must
     * meet at Loop Start. */
    n1 = head; 
    while (n1 != n2) { 
        n1 = n1.next; 
        n2 = n2.next; 
    }
    // Now n2 points to the start of the loop.
    return n2;
}

此解决方案的解释直接来自书本:

如果我们移动两个指针,其中一个指针带有
速度 1 和另一个速度 2,他们
如果已链接,则最终会面
列表有一个循环。为什么?想想两个
在轨道上行驶的汽车;更快的车
总是会超过较慢的那一个!

这里棘手的部分是找到起点
循环的。想象一下,打个比方,
两个人绕跑道赛跑,
一个人跑得比别人快一倍
其他。如果他们同时开始
地点,下次见面是什么时候?他们
将在下次会议开始时
下一圈。

现在,假设 Fast Runner 领先了 k 米
n 步一圈。他们下次什么时候
见面?他们将在 k 米之前相遇
下一圈的开始。 (为什么?快
跑步者会做出 k + 2(n - k)
步骤,包括领先优势,以及
慢跑者会做出 n - k
步骤 两者都将是之前的 k 步
循环开始)。

现在,回到问题,当 Fast Runner (n2) 和
慢跑者(n1)正在我们周围移动
循环链表,n2 将有一个
当 n1 时,循环开始
进入。具体来说,它将有一个
k 的开头,其中 k 是数字
循环之前的节点数。由于 n2 有
k 个节点 n1 和 n2 的领先优势
开始之前会遇到 k 个节点
循环。

所以,我们现在知道以下内容:

  1. Head 是来自 LoopStart 的 k 个节点(根据定义)
  2. n1 和 n2 的 MeetingPoint 是 LoopStart 中的 k 个节点(如上所示)

因此,如果我们将 n1 移回 Head 并将 n2 保留在 MeetingPoint,并以相同的速度移动它们,它们将在 LoopStart 处相遇

解决方案 2 上见面 - 由我提供:)

public static LinkedListNode findHeadOfLoop(LinkedListNode head) {

    int indexer = 0;
    Map<LinkedListNode, Integer> map = new IdentityHashMap<LinkedListNode, Integer>();
    map.put(head, indexer);
    indexer++;

    // start walking along the list while putting each node in the HashMap
    // if we come to a node that is already in the list, 
    // then that node is the start of the cycle 
    LinkedListNode curr = head;
    while (curr != null) {

        if (map.containsKey(curr.next)) {
            curr = curr.next;
            break;
        }
        curr = curr.next;
        map.put(curr, indexer);
        indexer++;
    }
    return curr;
}

我希望这会有所帮助。
赫里斯托

Solution 1 - courtesy of Career Cup and "Cracking the Coding Interview" book:

public static LinkedListNode findStartOfLoop(LinkedListNode head) {
    LinkedListNode n1 = head;
    LinkedListNode n2 = head; 

    // find meeting point using Tortoise and Hare algorithm
    // this is just Floyd's cycle detection algorithm
    while (n2.next != null) { 
        n1 = n1.next; 
        n2 = n2.next.next; 
        if (n1 == n2) { 
            break; 
        }
    }

    // Error check - there is no meeting point, and therefore no loop
    if (n2.next == null) {
        return null;
    }

    /* Move n1 to Head. Keep n2 at Meeting Point.  Each are k steps
    /* from the Loop Start. If they move at the same pace, they must
     * meet at Loop Start. */
    n1 = head; 
    while (n1 != n2) { 
        n1 = n1.next; 
        n2 = n2.next; 
    }
    // Now n2 points to the start of the loop.
    return n2;
}

The explanation for this solution is straight from the book:

If we move two pointers, one with
speed 1 and another with speed 2, they
will end up meeting if the linked
list has a loop. Why? Think about two
cars driving on a track; the faster car
will always pass the slower one!

The tricky part here is finding the start
of the loop. Imagine, as an analogy,
two people racing around a track,
one running twice as fast as the
other. If they start off at the same
place, when will they next meet? They
will next meet at the start of the
next lap.

Now, let’s suppose Fast Runner had a head start of k meters on
an n step lap. When will they next
meet? They will meet k meters before
the start of the next lap. (Why? Fast
Runner would have made k + 2(n - k)
steps, including its head start, and
Slow Runner would have made n - k
steps Both will be k steps before the
start of the loop ).

Now, going back to the problem, when Fast Runner (n2) and
Slow Runner (n1) are moving around our
circular linked list, n2 will have a
head start on the loop when n1
enters. Specifically, it will have a
head start of k, where k is the number
of nodes before the loop. Since n2 has
a head start of k nodes, n1 and n2
will meet k nodes before the start of
the loop.

So, we now know the following:

  1. Head is k nodes from LoopStart (by definition)
  2. MeetingPoint for n1 and n2 is k nodes from LoopStart (as shown above)

Thus, if we move n1 back to Head and keep n2 at MeetingPoint, and move them both at the same pace, they will meet at LoopStart

Solution 2 - courtesy of me :)

public static LinkedListNode findHeadOfLoop(LinkedListNode head) {

    int indexer = 0;
    Map<LinkedListNode, Integer> map = new IdentityHashMap<LinkedListNode, Integer>();
    map.put(head, indexer);
    indexer++;

    // start walking along the list while putting each node in the HashMap
    // if we come to a node that is already in the list, 
    // then that node is the start of the cycle 
    LinkedListNode curr = head;
    while (curr != null) {

        if (map.containsKey(curr.next)) {
            curr = curr.next;
            break;
        }
        curr = curr.next;
        map.put(curr, indexer);
        indexer++;
    }
    return curr;
}

I hope this helps.
Hristo

感情旳空白 2024-11-07 06:02:06

这个回答并不是为了争夺答案,而是为了进一步解释一下龟兔赛跑算法中两个节点的相遇。

  1. 两个节点最终都会进入循环。因为一个 (F) 移动得比另一个 (S) 快,所以 (F) 最终会绕 (S)。

  2. 如果循环的起点位于列表的头部,则 (F) 必须在列表的头部与 (S) 相遇。这只是因为 (F) 的速度是 (S) 的 2 倍;如果是 3X,则事实并非如此。这是正确的,因为当 (S) 完成半圈时,(F) 也完成一圈,因此当 (S) 完成第一圈时,(F) 已经完成两圈 - 并且与 (S) 一起回到循环起点.

  3. 如果循环的起点不在列表的头部,则当 (S) 进入循环时,(F) 已在循环中从 (k) 个节点开始。因为 (S) 的速度一次只有一个节点,所以它将在从循环开始起的 (k) 个节点处与 (F) 相遇 - 例如,在到达起点之前还需要 (k) 步,而不是在到达起点之后的 (k) 步开始。如果 (S) 的速度不是 1 并且 (F) 和 (S) 之间的速度比不是 2:1,则情况不成立。

    3.1。这就是解释起来有点棘手的地方。我们可以同意 (F) 将继续重叠 (S) 直到它们最终相遇(参见上面的 1),但是为什么在从循环开始起的 (k) 个节点处呢?考虑以下等式,其中 M 是节点数或环路距离,k 是领先起点 (F);该方程将左侧给定时间 t 下 (F) 行驶的距离换算为右侧 (S) 行驶的距离:

    d_F(t) = 2 * d_S(t) + k

    因此,当 (S) 进入循环并在循环中行进了 0 距离时,(F) 只行进了 (k) 距离。当 d_S = M - k 时,d_F = 2M - k。因为我们还必须使用模数学,考虑到 M 代表循环中单圈的总距离,所以 (F) 和 (S) 在任何整个 M(无余数)处的位置为 0。因此,根据POSITION(或微分),这留下 k(或更确切地说,-k)。

    最后,(S) 和 (F) 将在距离循环起点 (0 - k) 或 (k) 个节点处相遇。

  4. 根据上面的[3],as (k) 表示领先位置 (F),并且 as (F) 已经移动了从列表头部进入循环的距离 (S) 的 2 倍,(k)也表示距列表起点的距离,该距离代表循环的起点。

现在有点晚了,所以我希望我已经表达清楚了。否则请告诉我,我会尝试更新我的回复。

This response is not meant to compete for the answer, but rather to explain a little more on the meeting of the two nodes in the tortoise and the hare algorithm.

  1. Both nodes will eventually enter the loop. Because one moves faster (F) than the other (S), (F) will eventually lap (S).

  2. If the loop's start is at the list's head, (F) must meet (S) back at the list's head. This is ONLY because (F)'s speed is 2X (S)'s; if it was 3X this then would not be true. This is true because (F) completes one lap when (S) completes half a lap, so when (S) completes its first lap, (F) has completed two laps - and is back at the start of the loop with (S).

  3. If the loop's start is NOT at the list's head, then by the time (S) enters the loop, (F) has had a head start of (k) nodes in the loop. Because (S)'s speed is only one node at a time, it will meet (F) at (k) nodes from the loop's start - as in, (k) more steps before reaching the start, NOT (k) steps AFTER the start. This would NOT be true if (S)'s speed was not one and the speed ratio was not 2:1 between (F) and (S).

    3.1. This is where it gets a little tricky to explain. We can agree that (F) will continue lapping (S) until they eventually meet (see 1 above), but why at (k) nodes from the loop's start? Consider the following equation where M is the number of nodes or distance of the loop and k is the head start (F) had; the equation represents distance traveled by (F) given time t on the left in terms of distance traveled by (S) on the right:

    d_F(t) = 2 * d_S(t) + k

    So when (S) enters the loop and has traveled 0 distance in the loop, (F) has traveled only the (k) distance. By the time d_S = M - k, d_F = 2M - k. Because we also have to use modular math in consideration that M represents the total distance of a single lap in the loop, the POSITION of (F) and (S) at any whole M (no remainder) is 0. So then in terms of POSITION (or the differential), this leaves k (or rather, -k).

    And so finally, (S) and (F) will meet at position (0 - k), or (k) nodes away from the start of the loop.

  4. Given [3] above, as (k) represents the head start (F) had, and as (F) had traveled 2X the distance (S) traveled to enter the loop from the head of the list, (k) also represents the distance from the list's start, which then represents the start of the loop.

It's a bit late here so I hope I've articulated effectively. Let me know otherwise and I'll try and update my response.

烟─花易冷 2024-11-07 06:02:06

如果使用身份哈希映射(例如 IdentityHashMap) 是允许的,这非常容易解决。不过,它确实使用了更多空间。

遍历节点列表。对于遇到的每个节点,将其添加到身份映射中。如果该节点已存在于身份映射中,则列表具有循环链接,并且在此冲突之前的节点是已知的(在移动之前检查或保留“最后一个节点”)——只需根据需要设置“下一个”即可打破循环。

遵循这个简单的方法应该是一个有趣的练习:代码留给读者作为练习。

快乐编码。

If using an identity hash map (such as IdentityHashMap) is permissible this is terribly easy to solve. It does use more space, though.

Traverse the nodes list. For each node encountered, add it to the identity map. If the node already existed in the identity map then the list has a circular link and the node which was prior to this conflict is known (either check before moving or keep the "last node") -- simply set "next" as appropriate to break the cycle.

Following this simple approach should be a fun exercise: code is left as an exercise for the reader.

Happy coding.

何其悲哀 2024-11-07 06:02:06
 0--->1---->2---->3---->4---->5---->6
                  ▲                 |
                  |                 ▼
                 11<—-22<—-12<—-9<—-8  

在链表的每个节点之后插入虚拟节点,并在插入之前检查下一个节点是否为虚拟节点。如果 next to next 是虚拟的,则在该节点的 next 中插入 null。

 0-->D->1-->D->2-->D->3->D-->4->D-->5->D-->6
                     ▲                      |
                  /                         ▼
                 11<—D<-22<—D<-12<—D<-9<—D<--8 


Node(11)->next->next == D
Node(11)->next =null
 0--->1---->2---->3---->4---->5---->6
                  ▲                 |
                  |                 ▼
                 11<—-22<—-12<—-9<—-8  

Insert dummy node after every node of link list and before inserting check that node next to next is dummy or not. If next to next is dummy, insert null in next of that node.

 0-->D->1-->D->2-->D->3->D-->4->D-->5->D-->6
                     ▲                      |
                  /                         ▼
                 11<—D<-22<—D<-12<—D<-9<—D<--8 


Node(11)->next->next == D
Node(11)->next =null
毅然前行 2024-11-07 06:02:06
//Find a Loop in Linked List and remove link between node

    public void findLoopInList() {
            Node fastNode = head;
            Node slowNode = head;
            boolean isLoopExist = false;
            while (slowNode != null && fastNode != null && fastNode.next != null) {
                fastNode = fastNode.next.next;
                slowNode = slowNode.next;
                if (slowNode == fastNode) {
                    System.out.print("\n Loop Found");
                    isLoopExist = true;
                    break;
                }
            }
            if (isLoopExist) {
                slowNode = head;
                Node prevNode = null;
                while (slowNode != fastNode) {
                    prevNode = fastNode;
                    fastNode = fastNode.next;
                    slowNode = slowNode.next;
                }
                System.out.print("Loop Found Node : " + slowNode.mData);
                prevNode.next = null; //Remove the Loop
            }
        }

:)GlbMP

//Find a Loop in Linked List and remove link between node

    public void findLoopInList() {
            Node fastNode = head;
            Node slowNode = head;
            boolean isLoopExist = false;
            while (slowNode != null && fastNode != null && fastNode.next != null) {
                fastNode = fastNode.next.next;
                slowNode = slowNode.next;
                if (slowNode == fastNode) {
                    System.out.print("\n Loop Found");
                    isLoopExist = true;
                    break;
                }
            }
            if (isLoopExist) {
                slowNode = head;
                Node prevNode = null;
                while (slowNode != fastNode) {
                    prevNode = fastNode;
                    fastNode = fastNode.next;
                    slowNode = slowNode.next;
                }
                System.out.print("Loop Found Node : " + slowNode.mData);
                prevNode.next = null; //Remove the Loop
            }
        }

:)GlbMP

z祗昰~ 2024-11-07 06:02:06

最简单且独特的方法

为了解决这个问题,我们只需计算节点数(就是这样)。 我敢打赌,到目前为止,您还没有在任何竞争性网站上看到过这个解决方案,因为我今天自己做到了......

void removeTheLoop(Node *root)
{
    std :: unordered_set < struct Node * > s;
    if(root == NULL)
        return ;

    s.insert(root);
    int before_size = s.size();

    while(1)
    {
        if(root -> next == NULL)
            return;
        s.insert(root -> next);
        if(before_size == s.size())
        {
            root -> next = NULL;
            return;
        }
        before_size = s.size();
        root = root -> next;
    }
}

它是如何工作的:

时间复杂度:O(n)...空间复杂度:O(n)

  • 它只是计算元素的数量。我们将采用 c++ 中的 unordered_set 。
  • 如果容器中不存在该元素,它将插入该元素并增加其大小。
  • 现在,当节点指向已添加的节点时,悬念就开始了,因此在这种情况下,大小不会增加,我们会将其旁边的值设置为 NULL。

如果您认为它是独一无二的,请投票。

Easiest and unique way

To solve this problem we just count no of nodes (that's it). I bet you haven't seen this solution till now in any competitive website, because i made it today on my own...

void removeTheLoop(Node *root)
{
    std :: unordered_set < struct Node * > s;
    if(root == NULL)
        return ;

    s.insert(root);
    int before_size = s.size();

    while(1)
    {
        if(root -> next == NULL)
            return;
        s.insert(root -> next);
        if(before_size == s.size())
        {
            root -> next = NULL;
            return;
        }
        before_size = s.size();
        root = root -> next;
    }
}

How it works:

TIME COMPLEXITY: O(n)...SPACE COMPLEXITY: O(n)

  • It simply counts the no of elements. We will take unordered_set in c++.
  • It inserts the element if it is not present in the container and increases its size.
  • Now the suspense begins when the node comes that point to the node that already added , so in that case size doesn't increase and we will make next to that as NULL.

UPVOTE IT IF YOU THINK IT IS UNIQUE.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文