如何从几个非循环linekd列表的头节点数组中恢复一个循环链表?

How to restore a cyclic linked list from an array of head nodes of several acyclic linekd lists?

一个循环链表由多个非循环链表描述。从他们那里恢复循环链表。

本题也可以描述为:

给定几个非循环链表的头节点数组。建立一个与非循环链表中每个节点的继承关系完全匹配的循环链表。

假设:

很难用文字描述这个问题。请参阅下面的示例。

示例 1:

    Input: [[0, 1, 2], [1, 2, 3], [3, 4], [4, 0, 1]]
    Output: [0, 1, 2, 3, 4], where the next element of 4 is 0

示例 2:

    Input: [[3, 4], [0, 1], [2, 3], [1, 2], [4, 0], [0, 1, 2, 3, 4]]
    Output: [0, 1, 2, 3, 4], where the next element of 4 is 0.

示例 3:

    Input: [[1, 2, 0, 6], [2, 0, 6, 7], [7, 9], [3, 4], [7, 9, 10, 3], [4, 5, 1]] 
    Output: [3, 4, 5, 1, 2, 0, 6, 7, 9, 10], where the next element of 10 is 3.

我下面的代码似乎可以工作,但效率肯定很低。我想知道是否有更优雅的方法来做到这一点。

import java.util.*;

public class Test {
    public static void main(String[] args) {
        List<ListNode> src = new ArrayList<>();
        ListNode temp = new ListNode();
        temp.val = 1;
        temp.next = new ListNode();
        temp.next.val = 2;
        temp.next.next = new ListNode();
        temp.next.next.val = 0;
        temp.next.next.next = new ListNode();
        temp.next.next.next.val = 6;
        src.add(temp);

        temp = new ListNode();
        temp.val = 2;
        temp.next = new ListNode();
        temp.next.val = 0;
        temp.next.next = new ListNode();
        temp.next.next.val = 6;
        temp.next.next.next = new ListNode();
        temp.next.next.next.val = 7;
        src.add(temp);

        temp = new ListNode();
        temp.val = 7;
        temp.next = new ListNode();
        temp.next.val = 9;
        src.add(temp);

        temp = new ListNode();
        temp.val = 3;
        temp.next = new ListNode();
        temp.next.val = 4;
        src.add(temp);

        temp = new ListNode();
        temp.val = 7;
        temp.next = new ListNode();
        temp.next.val = 9;
        temp.next.next = new ListNode();
        temp.next.next.val = 10;
        temp.next.next.next = new ListNode();
        temp.next.next.next.val = 3;
        src.add(temp);

        temp = new ListNode();
        temp.val = 4;
        temp.next = new ListNode();
        temp.next.val = 5;
        temp.next.next = new ListNode();
        temp.next.next.val = 1;
        src.add(temp);


        ListNode res = solve(src);
        ListNode p = res.next;
        System.out.println(res.val);
        while (p != res) {
            System.out.println(p.val);
            p = p.next;
        }
    }

    static class ListNode {
        int val;
        ListNode next;
    }

    //n ^ 2 k
    static ListNode solve(List<ListNode> nodes) {
        // partly restored circular list. the first element is connected to the last element
        List<Integer> res = new ArrayList<>();
        // values that have been added to res.
        Set<Integer> set = new HashSet<>();
        // add the first linked list to res
        ListNode head = nodes.get(0);
        while (head != null) {
            res.add(head.val);
            set.add(head.val);
            head = head.next;
        }
        // remove the first linked list from unvisited linked lists, because all of its nodes have been added to res
        nodes.remove(0);
        // visit the ith linked list of nodes in the following loop
        int i = 0;
        // while there is still linked list that hasn't been visited.
        while (!nodes.isEmpty()) {
            ListNode p = nodes.get(i);
            // there is overlap between res and the linked list we are visiting
            if (set.contains(p.val)) {
                // find the first element that hasn't been added to res.
                p = p.next;
                while (p != null && set.contains(p.val)) {
                    p = p.next;
                }
                // since no duplicated value exists, position of the unoverlaped section, which starts at p and ends at unknowns, can be added to the tail of res, the circular linked list.
                while (p != null && !set.contains(p.val)) {
                    res.add(p.val);
                    set.add(p.val);
                    p = p.next;
                }
            } else { // maybe there is overlap, or maybe not. we don't know yet.
                // overlap may occurs later in the linked list we are visiting. find it out
                ListNode temp = p;
                while (temp != null && !set.contains(temp.val)) {
                    temp = temp.next;
                }
                // no overlap. So the position of the linked list cannot be determined. continue to visit the next linked list.
                if (temp == null) {
                    i++;
                    continue;
                }
                // there is overlap from temp to end. But there is no overlap from p to temp - 1.
                // So, we can know that the section that starts at p and ends at temp - 1 is in front of res. So, add res to it
                // it seems redundant here? I can add the section to res since it's a circular list
                //p -> temp
                List<Integer> temp2 = new ArrayList<>();
                while (p != temp) {
                    temp2.add(p.val);
                    set.add(p.val);
                    p = p.next;
                }
                temp2.addAll(res);
                res = temp2;
            }
            // ith linked list has been visited and all of its nodes have been added to res. remove it from unvisited list and try to visit all linked lists again.
            nodes.remove(i);
            i = 0;
        }
        // construct circular linked list to return
        ListNode dummyHead = new ListNode();
        ListNode p = dummyHead;
        for (int val : res) {
            p.next = new ListNode();
            p.next.val = val;
            p = p.next;
        }
        p.next = dummyHead.next;
        return dummyHead.next;
    }

}

您可以维护一个父指针或下一个指针的向量,指定哪个元素是给定元素的父元素(或下一个元素)。例如,在解析输入 L=[[0,1,2],...] 时,设置 next[0]=1。向量 next 可能没有连续的索引,在 Python 中,字典很方便用于此目的,因为索引不必是连续的整数。例如,在下面的代码中,L 不包含元素 8。这里是 Python 中的代码:

L = [[1, 2, 0, 6], [2, 0, 6, 7], [7, 9], [3, 4], [7, 9, 10, 3], [4, 5, 1]] 
    
next = {}
for cycle in L:
    m = len(cycle)
    for i in range(m-1):  #for i=0,1,...,m-2
        #make cycle[i+1] the next value of cycle[i]
        next[cycle[i]] = cycle[i+1]

#print res = [i, next[i], next[next[i]]...]
i = list(next.keys())[0]
res = [i]
y = next[i]
while y!= i:
    res.append(y)
    x = y
    y = next[y]
print(str(res) + ", where the next element of " +  str(x) +  " is " + str(i))         

输出:

[1, 2, 0, 6, 7, 9, 10, 3, 4, 5], where the next element of 5 is 1

使用 HashMap 来跟踪一个接一个的值。这样你会找到所有链接,也许只有一个链接除外。通过跟踪您还不知道前任的值(随着您处理列表而变得更小),您最终会得到一个这样的值,它可以用来进行循环。

我还会通过为 ListNode:

定义一些构造函数来简化一些初始化代码
    static ListNode createList(int... arr) {
        ListNode head = null;
        for (int j = arr.length - 1; j >= 0; j--) {
            head = new ListNode(arr[j], head); 
        }
        return head;
    }

    
    public static void main(String[] args) {
        List<ListNode> src = new ArrayList<>(
            Arrays.asList(
                createList(1, 2, 0, 6),
                createList(2, 0, 6, 7),
                createList(7, 9),
                createList(3, 4),
                createList(7, 9, 10, 3),
                createList(4, 5)
            )
        );

        ListNode res = solve(src);
        res.print();
    }

    static class ListNode {
        int val;
        ListNode next;

        ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }

        ListNode(int val) {
            this.val = val;
            this.next = null;
        }

        void print() {
            System.out.print(this.val + " ");
            ListNode node = this.next;
            while (node != this && node != null) {
                System.out.print(node.val + " ");
                node = node.next;
            }
            System.out.println();
        }
    }

    static ListNode solve(List<ListNode> nodes) {
        if (nodes.size() == 0) {
            return null;
        }
        HashMap<Integer, Integer> map = new HashMap<>();
        HashSet<Integer> roots = new HashSet<>();
        // Collect potential head values
        for (var node : nodes) {
            roots.add(node.val);
        }
        // Link every value with its successor
        for (var node : nodes) {
            while (node != null) {
                if (node.next != null) {
                    map.put(node.val, node.next.val);
                    roots.remove(node.next.val);
                } else if (!map.containsKey(node.val)) {
                    map.put(node.val, node.val); // temporary self ref
                }
                node = node.next;
            }
        }
        // Create the circular list based on the map
        ListNode head = new ListNode(nodes.get(0).val);
        ListNode node = head;
        while (true) {
            int next = map.get(node.val);
            if (next == node.val) { // Self ref: we don't know next
                for (int val : roots) { // Will only iterate once
                    next = val; // Take the value without predecessor
                }
            }
            if (next == head.val) { // Back to beginning
                node.next = head; // Make the cycle
                return head;
            }
            node = node.next = new ListNode(next);
        }
    }