【问题标题】:Invert linear linked list反转线性链表
【发布时间】:2011-06-08 19:57:44
【问题描述】:

线性链表是一组节点。这就是节点的定义方式(为方便起见,我们不区分节点和列表):

class Node{
    Object data;
    Node link;

    public Node(Object pData, Node pLink){
        this.data = pData;
        this.link = pLink;
    }

    public String toString(){
        if(this.link != null){
            return this.data.toString() + this.link.toString();
        }else{
            return this.data.toString() ;
        }
    }

    public void inc(){
        this.data = new Integer((Integer)this.data + 1);
    }

    public void lappend(Node list){
        Node child = this.link;
        while(child != null){
            child = child.link;
        }
        child.link = list;
    }

    public Node copy(){
        if(this.link != null){
            return new Node(new Integer((Integer)this.data), this.link.copy());
        }else{
            return new Node(new Integer((Integer)this.data), null);
        }
    }

    public Node invert(){
        Node child = this.link;
        while(child != null){
            child = child.link;
        }
        child.link = this;....
    }
}

我能够制作列表的深层副本。现在我想反转列表,以便第一个节点是最后一个,最后一个是第一个。倒排列表必须是深拷贝。

我开始开发反转功能,但我不确定。有什么想法吗?

更新:也许有一种递归方式,因为线性链表是一种递归数据结构。

我会获取第一个元素,遍历列表直到我到达一个没有子节点的节点并附加第一个元素,我会在第二个,第三个重复这个......

【问题讨论】:

  • 如果需要双向遍历,可以双链表。
  • 如果你在 stackoverflow 中搜索“反向链表”这个短语,你会发现很多答案,其中一些是在 Java 中。
  • 我认为递归虽然是一种非常酷的思考方式(如果您试图扩展您对问题的思考方式,这绝对是好的),但没有必要。沿着堆栈和队列的思路思考。:)
  • @skaffman deep-copy 不适用于反转集合/列表。在最好的情况下,它可以被视为浅拷贝。

标签: java algorithm linked-list deep-copy


【解决方案1】:

我有时会在面试中问这个问题...

我不建议使用递归解决方案,使用堆栈来解决这个问题。为这样的任务分配 O(n) 内存是没有意义的。

这是一个简单的 O(1) 解决方案(我现在没有运行它,如果需要更正,我深表歉意)。

Node reverse (Node current) {

    Node prev = null;

    while (current != null) {
        Node nextNode = current.next;
        current.next = prev;
        prev = current;
        current = nextNode;
    }

    return prev;
}

顺便说一句:lappend 方法有效吗?似乎它总是会抛出一个NullReferenceException

【讨论】:

  • 看来您必须遍历列表的整个长度才能反转它。这是一个 O(n) 的解决方案吗?
  • @David: O(1) 内存,不是时间。
  • @Ran,您错过了将第一个节点与 null 相邻的链接。
【解决方案2】:

根据以下观察,有一个很好的递归解决方案:

  1. 空列表的反面就是空列表。
  2. 单例列表的反面就是它本身。
  3. 节点 N 后跟列表 L 的列表的反向是列表 L 后跟节点 N 的反向。

因此,您可以使用伪代码实现反向功能:

void reverseList(Node node) {
    if (node == null) return;      // Reverse of empty list is itself.
    if (node.next == null) return; // Reverse of singleton list is itself.

    reverseList(node.next); // Reverse the rest of the list
    appendNodeToList(node, node.next); // Append the new value.
}

这个算法的一个简单实现在 O(n2) 中运行,因为每次反转都需要一个追加,这需要对列表的其余部分进行 O(n) 扫描。但是,您实际上可以通过巧妙的观察在 O(n) 中完成这项工作。假设您有一个如下所示的链表:

n1 --> n2 --> [rest of the list]

如果你反转从 n2 开始的列表,那么你最终会得到这个设置:

n1       [reverse of rest of the list] --> n2
 |                                          ^
 +------------------------------------------+

因此,您可以通过设置 n1.next.next = n1 将 n1 附加到列表其余部分的反向,这会将反向列表的新结尾 n2 更改为指向 n1:

[reverse of the rest of the list] --> n2 --> n1

你是金子!又是更多伪代码:

void reverseList(Node node) {
    if (node == null) return;      // Reverse of empty list is itself.
    if (node.next == null) return; // Reverse of singleton list is itself.

    reverseList(node.next); // Reverse the rest of the list
    node.next.next = node; // Append the new value.
}

编辑:正如 Ran 所指出的,这使用调用堆栈作为其存储空间,因此存在堆栈溢出的风险。如果您想改用显式堆栈,可以这样做:

void reverseList(Node node) {
    /* Make a stack of the reverse of the nodes. */
    Stack<Node> s = new Stack<Node>();
    for (Node curr = node; node != null; node = node.next)
        s.push(curr);

    /* Start unwinding it. */
    Node curr = null;
    while (!s.empty()) {
        Node top = s.pop();

        /* If there is no node in the list yet, set it to the current node. */
        if (curr == null)
            curr = top;
        /* Otherwise, have the current node point to this next node. */
        else
            curr.next = top;

        /* Update the current pointer to be this new node. */
        curr = top;
    }
}

我相信这同样会反转链表元素。

【讨论】:

  • 在任何实际应用中,这种方法非常不推荐。请注意,此解决方案的内存成本为 O(n),其中 n 是节点数。更糟糕的是,所有这些内存都在堆栈上,而不是堆上 - 这意味着对于长列表,您将因堆栈溢出而崩溃。
  • @Ran- 非常正确。如果有必要,我将添加关于如何使用显式堆栈的评论。
  • 为什么不使用 O(1) 解决方案...? :)
  • @Ran- 我想我们正在解决不同的问题。如果您想 重新排序 节点,我不知道在 O(1) 中执行此操作的方法;有一个著名的解决方案吗?如果您想复制节点,那么您可以使用 O(n) 内存(用于复制)但只有 O(1) 开销。你说的是这个吗?
  • 我认为这里的问题是如何反转链表。不是吗?如果是,那么可以使用 O(1) 内存来完成。看我的回答...
【解决方案3】:

我会将当前列表视为堆栈(这是我的伪代码):

Node x = copyOf(list.head);
x.link = null;
foreach(node in list){
    Node temp = copyOf(list.head);
    temp.link = x;
    x = temp;        
}

最后x 将是反向列表的头部。

【讨论】:

    【解决方案4】:

    我比较熟悉whit C,但还是让我试试吧。 (我只是不确定这是否在 Java 中运行,但它应该)

    node n = (well first one)
    node prev = NULL;
    node t;
    while(n != NULL)
    {
         t = n.next;
         n.next = prev;
         prev = n;
         n = t;
    }
    

    【讨论】:

      【解决方案5】:

      反转单链表是一个经典的问题。它也在这里得到了回答(并且得到了很好的回答),它不需要递归也不需要额外的内存,除了一个寄存器(或 2 个)用于保持参考。 但是对于 OP,我想这是一个学校项目/家庭作业和一些建议,如果您曾经使用单链表进行一些真实的数据存储,请考虑使用尾节点。 (到目前为止,单链表几乎已经绝迹,不过想到了 HashMap 存储桶)。 除非您必须在“添加”期间检查所有节点的某些情况,否则尾部是一个相当大的改进。下面是一些具有反向方法和尾节点的代码。

      package t1;
      
      public class SList {
          Node head = new Node(); 
          Node tail = head;
      
          private static class Node{
              Node link;
              int data;           
          }
      
      
          void add(int i){
              Node n = new Node();
              n.data = i;
              tail = tail.link =n;        
          }
      
          void reverse(){
              tail = head;
              head = reverse(head);
              tail.link = null;//former head still links back, so clear it
          }
      
          private static Node reverse(Node head){            
              for (Node n=head.link, link; n!=null; n=link){//essentially replace head w/ the next and relink
                  link = n.link;
                  n.link = head;
                  head = n;           
              }
              return head;
          }
      
          void print(){
              for (Node n=head; n!=null;n=n.link){
                  System.out.println(n.data);
              }       
          }
      
          public static void main(String[] args) {
              SList l = new SList();
              l.add(1);l.add(2);l.add(3);l.add(4);
              l.print();
              System.out.println("==");
              l.reverse();
              l.print();
          }
      }
      

      【讨论】:

        【解决方案6】:

        我想知道类似的事情(我没有测试过,所以):

        invert(){
          m(firstNode, null);
        }
        
        m(Node v, Node bef){
          if(v.link != null)
           m(v.link,v);
          else
           v.link=bef;
        }
        

        【讨论】:

          【解决方案7】:

          无需太多测试,

          Node head = this;
          Node middle = null;
          Node trail = null;
          while (head != null) {
              trail = middle;
              middle = head;
              head = head.link;
              middle.link = trail;
          }
          head = middle;
          return head;
          

          【讨论】:

            【解决方案8】:
            public ListNode Reverse(ListNode list)
            {
                if (list == null) return null; 
            
                if (list.next == null) return list; 
            
                ListNode secondElem = list.next;
            
                ListNode reverseRest = Reverse(secondElem);
            
                secondElem.Next = list;
            
                return reverseRest;
            }
            

            希望这会有所帮助。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-05-21
              • 2016-09-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多