【问题标题】:how to reverse a list with O(1) space and O(n) time?如何反转具有 O(1) 空间和 O(n) 时间的列表?
【发布时间】:2011-08-24 12:42:49
【问题描述】:

我正在寻找一种反转给定列表的相同实例的方法,具有 O(1) 额外空间和 O(n) 时间。
这不是硬件,我也不是在寻找一些图书馆方法来为我完成这项工作,因为这只是我自己的练习,纯粹是出于好奇。

任何想法如何用 O(1) 额外空间和 O(n) 时间做到这一点? (如果可能的话,也无需反思)?
签名是public <T> void reverse(List<T> list)

(*)假设 get() 到列表的头部和尾部是 O(1),但到它的中间是 O(n)。

我想出了一个递归解决方案,但它是 O(n) 空间,O(n) 时间

public <T> void reverseAux(List<T> list,int size) {
    if (size == 0) return;
    T elem = list.remove(size-1);
    reverseAux(list,size-1);
    list.add(0,elem);
}
public <T> void reverse(List<T> list) {
    reverseAux(list, list.size());
}

编辑:我正在寻找一个 java 解决方案,对于 List&lt;T&gt;,实现的唯一假设是头部和尾部的访问时间 O(1),并使用 List&lt;T&gt; 接口。

【问题讨论】:

  • 你不能简单地从 0 迭代到 n/2 并交换 i 和 n - i 吗?在 for 循环中,而不是 foreach...
  • @Max: get() is O(n) for the middle of the list, and swapping requires me access n-i, and I get O(n^2)
  • @amit 那么我真的很抱歉...为什么不能使用具有恒定时间索引的容器?
  • 你不能只是假设它是一个 Java List&lt;T&gt;;这不会让你做出那些类型约束(例如,你不能假设索引是 O(n) 或其他),甚至必须修改列表根本。 :-)
  • @amit:在这种情况下,我认为您应该问有关单链表的问题,而不是问有关List 接口的问题,我认为这很明显是为了表示“最坏”的双向链表。

标签: java algorithm list


【解决方案1】:

只需阅读以下内容之一。这就是你所说的。

请注意,我们讨论的是单个“链接”列表。

http://www.teamten.com/lawrence/writings/reverse_a_linked_list.html

http://www.mytechinterviews.com/reverse-a-linked-list

http://www.geekpedia.com/code48_Reverse-a-linked-list.html

http://www.codeproject.com/KB/recipes/ReverseLinkedList.aspx

另外还有一个问题要问你:

假设 Nth 元素是单链接的,并且你只有 O(1) 空间和 O(N) 时间的头指针,你将如何从链表的尾部找到 Nth 元素?

【讨论】:

  • 给你一个额外的问题:假设它是单链接的并且你只有没有 O(1) 空间和 O 的头指针,你如何从链表的尾部找到 Nth 元素(N) 时间?
  • @ahmet:只需对列表进行两次传递。首先找到长度,然后减去以获取索引并从头开始再次遍历以找到该索引。 O(2n) = O(n).
  • @ahmet,更快的解决方案是:从列表头部的指针开始,遍历 N 个元素,然后添加第二个指针,并遍历两个指针(相等地递增),直到第一个指针到达终点。
  • @ahmet:我知道 Nodes 的解决方案,我想看看我是否遗漏了一些东西,或者实际上,仅使用 List 接口,我试图做的事情是不可能的。 . 你的链接读起来很不错。为此+1。 :)
  • 不正确。 -&gt;next 的数量使用 ikegami 的方法:size-N 或 2*size-N。使用 davin 的方法:N+2*(size-N) = 2*size-N。 ikegami 的方法至少与 davin 的方法一样好,如果列表跟踪其大小,则更好。奖励:ikegami 的方法应该会减少缓存未命中。
【解决方案2】:

使用 ListIterators:

ListIterator<T> head = list.listIterator();
ListIterator<T> tail = list.listIterator(size);//assuming this can be done in O(1) though O(n) doesn't hurt that much and total is still O(n)
while(head.nextIndex()<tail.previousIndex()){
    T tmp = head.next();
    head.set(tail.previous());
    tail.set(tmp);
}

【讨论】:

  • @ratchet:首先我认为这就是我要找的东西,但我不确定第二个想法我们可以假设 prviousIndex() 是 O(1)...:\
  • previousIndex() 太容易不做了。但确实以前不能在 O(1) 中在单链表上完成,虽然有一些方法可以在 O(n) 时间和 O(1) 空间中反转单链表,但它非常具体(从源中弹出并推送to dest) 到它们,然后基于数组的列表将在同一算法上运行 O(n^2) 和/或 O(n) 内存,具体取决于(移动数组)或 dest 预分配
  • 您不需要使用previousIndex()。您可以保留自己的柜台。但是,从LinkedList 返回的列表迭代器对previousIndex() 有一个 O(1) 实现。
  • @ahmet - 您在哪里看到不稳定的空间需求?
  • @amit:我认为我们可以假设(请参阅主要问题下方的我的 cmets)。 Java 的List 接口在复杂性方面未指定,但如果我们不能假设previous 和/或previousIndex 是O(1)(因为它可能是一个单链表),那么我们不能假设 nextnextIndex 两者都是(因为它可能是一个反向单链表)。在文档的字里行间阅读一下,我认为List 必须是双重链接,而不是单一链接。
【解决方案3】:

你已经知道长度了。所以只需使用 1 个临时变量并从索引 0 开始,然后继续交换 list[0] 和 list[length -1],然后是 list[1] 和 list[length-2],依此类推。 1 个临时变量的 O(n) 时间和 O(1) 空间。

编辑:刚刚注意到您假设 O(n) 用于访问列表的中间。那好吧。没关系。

或者,存储您交换的两个元素的下一个/上一个指针以向中间移动(假设它是一个双向链表)。然后你得到 O(n) 时间。

【讨论】:

  • 他说的是列表,而不是数组。
  • 与 list[length-i] 交换 i->n/2 是 O(n),所以这个解决方案将使它成为 O(n^2) 时间
  • 是的,正如我提到的,我刚刚注意到您无法在恒定时间内随机访问它。见编辑。
  • 这适用于双向链表和两个指针。 O(1) 空间,O(n) 时间。
  • 编辑了问题,我正在寻找 List 接口的 java 实现。
【解决方案4】:

如前所述,在一般情况下这是不可行的,您需要对各个操作的复杂性进行假设。如果迭代器有恒定时间 next()previous(),请使用已经给出的解决方案。它应该适用于 LinkedList 和 ArrayList。

我想到了一种适用于单链表的解决方案(但不适用于 ArrayList 之类的东西),但遗憾的是 ListIterators add 方法将元素插入光标之前而不是光标之后,因此它不可行使用 List + ListIterator 接口(如果我们无法修补 ListIterator 实现以缓存预插入元素以允许在 O(1) 中 add 之后出现单个 previous())。

在这里,假设一个简单的Node 类带有下一个指针:

/**
 * reverses a singly linked list.
 * @param first the fist node. This will be the new last node.
 * @param last the last node. This will be the new first node.
 */
void reverseList(Node first, Node last) {
   while(first != last) {
      Node temp = first;
      first = temp.next;
      temp.next = last.next;
      last.next = temp;
   }
}

在索引方面,这将是这样的:

public void reverseList(List<T> list) {
    int index = list.size() -1;
    while(n > 0) {
       T element = list.remove(0);
       list.add(n, element);
       n--;
    }
}

在 ListIterator 术语中,这将是这样的:

public void reverseList(List<T> list) {
    ListIterator<T> it = list.listIterator(list.size());
    while(it.previousIndex() > 0) { // we could count ourself here, too
       T element = list.remove(0);
       it.add(element);
       it.previous();
    }
}

当然,通常的单链表实现不会有 O(1) previous 实现,因此它不会在那里工作,如前所述。 (他们可能会抛出 ConcurrentModificationException,或者返回错误的previousIndex。)

【讨论】:

    【解决方案5】:

    这是 Java 中的一个解决方案,时间复杂度为 O(n)(仅一次通过)和 O(1) 空间复杂度(仅使用两个临时变量):

        private static void reverseLinkedList() {//O(n) Time complexity, O(1) space complexity 
    
        //two temp pointers
        Node next = null, previous = null;
    
        while(head.next != null){
            next = head.next;//move next to next address
            head.next = previous;   //previous node will be the next node for head, so that head will point reverse         
            previous = head; //incrementing previous to the current node
            head = next; //incrementing head 
    
        }
        //at this point head points to last node and previous has the remaining reversed array
        head.next = previous;
        System.out.println("\nReversed");
    
    }
    

    完整代码如下:

      package com.test;
    
    public class LinkedListReverse {
        private static Node  head;
        public static void main(String[] args) {
    
            for(int i = 0 ; i< 10 ; i++){
                addToLinkedList(i);
            }
            System.out.println("Added Data");
    
            printLinkedList();
            reverseLinkedList();
            printLinkedList();
    
    
        }
    
    
    
        private static void reverseLinkedList() {//O(n) Time complexity, O(1) space complexity 
    
            //two temp pointers
            Node next = null, previous = null;
    
            while(head.next != null){
                next = head.next;//move next to next address
                head.next = previous;   //previous node will be the next node for head, so that head will point reverse         
                previous = head; //incrementing previous to the current node
                head = next; //incrementing head 
    
            }
            //at this point head points to last node and previous has the remaining reversed array
            head.next = previous;
            System.out.println("\nReversed");
    
        }
    
    
        /* Logic for adding and printing linked list*/
        private static void printLinkedList() {
            System.out.println("Printing linked list");
            Node temp = head;
            while(temp.next != null){
                System.out.print(temp.value+" ");
                temp = temp.next;
            }
            System.out.print(temp.value+" ");//print the value at the last node
    
    
        }
    
        private static void addToLinkedList(int value){
            if(head == null){
                head = new Node(value, null);
            }else{
                Node temp = head;
                while(temp.next != null){
                    temp = temp.next;
                }
                temp.next = new  Node(value, null);
            }
        }
    
    }
    
    //Linked List definition 
    class Node{
        int value;
        Node next;
        public Node(int value, Node next){
            this.value = value;
            this.next = next;
        }
    }
    

    程序的输出:

    Added Data
    Printing linked list
    0 1 2 3 4 5 6 7 8 9 
    Reversed
    Printing linked list
    9 8 7 6 5 4 3 2 1 0 
    

    希望对你有帮助:)

    【讨论】:

    • 这个想法是与java的List接口一起工作
    【解决方案6】:

    从比较排序(如合并排序或快速排序)中可以获得的最佳性能是 O(nlogn)。您可以从基数排序等非比较排序中获得 O(n) 性能。

    如果您正在反转一个链表,那么您可以在 O(n) 时间内反转该列表,只需使用 3 个额外的项目。您需要 3 个指针来跟踪您当前指向的内容、当前项目之前的内容以及当前项目之后的内容。代码是:

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

    【讨论】:

      【解决方案7】:

      ListIterator 接口是您正在寻找的接口(合理假设相关列表完全支持它;ArrayListLinkedList 都可以):

      ListIterator<T> fwd = list.listIterator();
      ListIterator<T> back = list.listIterator(list.size());
      while (fwd.nextIndex() < back.previousIndex()) {
          T tmp = fwd.next();
          fwd.set(back.previous());
          back.set(tmp);
      }
      

      即使在链表上,这也应该是线性的。

      【讨论】:

      • 你从我这里得到的,这是代码的近乎完美的副本;)
      【解决方案8】:
         public LinkedList Reverse(LinkedList head)
      {
          if (head == null) return null; // first question
      
          if (head.Next == null) return head; // second question
      
          // third question
          // so we grab the second element (which will be the last after we reverse it)
      
          LinkedList secondElem = head.Next;
      
          // bug fix - need to unlink list from the rest or you will get a cycle
          head.Next = null;
      
          // then we reverse everything from the second element on
          LinkedList reverseRest = Reverse(secondElem);
      
          // then we join the two lists
          secondElem.Next = head;
      
          return reverseRest;
      }
      

      【讨论】:

      • 这似乎是 O(n) 空间,用于递归调用的堆栈跟踪,而问题要求 O(1) 空间。
      猜你喜欢
      • 1970-01-01
      • 2015-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-28
      • 1970-01-01
      • 1970-01-01
      • 2011-08-09
      相关资源
      最近更新 更多