我正在寻找一种方法来反转给定列表的相同实例,具有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());
}
编辑:我正在为List<T>
寻找一个java解决方案,只有实现的假设是头部和尾部的访问时间O(1),并使用List<T>
接口
答案 0 :(得分:15)
请阅读以下其中一项。这是你在谈论的事情。
请注意,我们正在谈论单独的'链接'列表。
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
另外还有一个问题:
你如何从链表的尾部找到
N
元素,假设它是单链接的,你只有头指针有O(1)空格和O(N)时间?
答案 1 :(得分:4)
使用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);
}
答案 2 :(得分:2)
你已经知道了长度。因此,只需使用1个临时变量并从索引0开始,然后继续交换列表[0]并列出[length -1],然后列出[1]并列出[length-2],依此类推。 O(n)时间和O(1)空间为1个临时变量。
编辑:刚刚注意到你假设O(n)访问列表的中间位置。那好吧。没关系。
或者,存储你交换的两个元素的下一个/前一个指针向中间移动(假设它是一个双向链表)。然后你得到O(n)时间。
答案 3 :(得分:0)
ListIterator
界面是您正在寻找的(在合理的假设下,相关列表完全支持它; ArrayList
和LinkedList
都这样做):
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);
}
即使在链表上,这也应该是线性的。
答案 4 :(得分:0)
如上所述,在一般情况下,这是不可行的,您需要假设某些操作的复杂性。如果迭代器有常量next()
和previous()
,请使用已经给出的解决方案。它应该适用于LinkedList和ArrayList。
我想到了一个适用于单链表的解决方案(但不适用于像ArrayList这样的东西),但遗憾的是ListIterators add
方法在光标之前而不是之后插入元素,因此它是不适用于List + ListIterator接口(如果我们无法修补ListIterator实现以缓存插入前元素以允许在O(1)中previous()
之后的单个add
。
这里,假设一个带有next-pointer的简单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 :(得分:0)
从合并排序或快速排序等比较排序中获得的最佳性能是O(nlogn)。您可以从基数排序等非比较排序中获得O(n)性能。
如果要反转链接列表,则可以使用仅3个额外项目在O(n)时间内反转列表。您需要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;
答案 6 :(得分:-1)
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;
}