Как изменить список с O (1) пространством и временем O (n)?

Я ищу метод, который меняет один и тот же экземпляр данного списка, с O (1) дополнительным пространством и временем O (n).
это не HW, и я не ищу какой-нибудь библиотечный метод для выполнения этой работы для меня, поскольку это только упражнение для меня и из чистого любопытства.

любые идеи, как это сделать с 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());
}

EDIT: Я ищу Java-решение, для List<T>, только предположение о реализации - это время доступа O (1) для головы и хвоста и использование интерфейса List<T>.

Ответы

Ответ 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

Плюс дополнительный вопрос для вас:

Как вы находите N -й элемент из хвоста связанного списка, если он связан по-отдельности, и у вас есть только указатель на голову с O (1) пространством и временем O (N)?

Ответ 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);
}

Ответ 3

Вы уже знаете длину. Так что просто используйте 1 временную переменную и начинайте с индекса 0 и переходите на список подкачки [0] и укажите [длина -1], затем список [1] ​​и список [длина-2] и т.д. O (n) и O (1) для 1 временной переменной.

EDIT: Только что вы заметили, что вы используете O (n) для доступа к середине списка. Ну что ж. фига.

в качестве альтернативы, сохраните следующие/предыдущие указатели двух элементов, которые вы поменяли, чтобы перейти к середине (при условии, что это двойной список). Затем вы получаете O (n) время.

Ответ 4

Интерфейс ListIterator - это то, что вы ищете (в разумных предположениях, что этот список полностью поддерживает его, как ArrayList, так и LinkedList do):

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);
}

Даже в связанных списках это должно быть линейным по времени.

Ответ 5

Как обсуждалось, в общем случае это не выполнимо, вам нужно взять что-то о сложности отдельных операций. Если для итераторов есть константы времени next() и previous(), используйте уже заданное решение. Он должен работать как для LinkedList, так и для ArrayList.

Я подумал о решении, которое будет работать для односвязного списка (но не для чего-то вроде ArrayList), но, к сожалению, метод ListIterators add вставляет элемент перед курсором, а не после него, поэтому он не выполним с интерфейсами List + ListIterator (если мы не можем исправить реализацию ListIterator для кэширования элемента pre-insert, чтобы разрешить одиночный previous() после add в O (1)).

Здесь, предполагая простой класс 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 или возвращать erronous previousIndex.)

Ответ 6

Вот решение на 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 

Надеюсь, что это поможет:)

Ответ 7

Наилучшая производительность, которую вы можете получить из сортировок сравнения, таких как сортировка слияния или быстрая сортировка, - это O (nlogn). Вы можете получить производительность O (n) из не-сортировки, например сортировку по методу radix.

Если вы перевернули связанный список, вы можете отменить список в 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;

Ответ 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;
}