Wywiad: usuń pętlę na liście linked - Java

Zadano mi to pytanie w wywiadzie: "jak wykryć pętlę w liście połączonej?", Rozwiązałem to, ale natychmiast rozmówca zapytał mnie, jak usunąć pętlę w połączonej liście. Wpadłem.

Więc jakieś wskazówki, jak to rozwiązać, może być pseudo kod, lub definicja metody?

Jestem zadowolony z Javy, więc otagowałem to pytanie Pod java.

Na przykład ta połączona lista ma pętlę

 0--->1---->2---->3---->4---->5---->6
                  ▲                 |
                  |                 ▼
                 11<—-22<—-12<—-9<—-8
Author: Algorithmist, 2011-04-09

6 answers

Istnieją dwie części tego problemu:

  1. Wykryj, czy na liście jest pętla
  2. Zidentyfikuj początek pętli

Gdy wiesz, gdzie zaczyna się pętla, łatwo jest zidentyfikować ostatni element na liście, ponieważ jest to element na liście po początku pętli, który kończy się wskazaniem do początku pętli. Jest wtedy trywialne, aby ustawić następny wskaźnik / odniesienie do tego elementu na null, aby skorygować listę linków cyklicznych (nie kołowych linked list, czyli gdzie ostatnie elementy wskazują na pierwsze-byłaby to specyficzna instancja list cyklicznych).

  1. Floyd ' s cycle detect algorithm, zwany również algorytmem żółwia i zająca , ponieważ polega na użyciu dwóch wskaźników / odniesień, które poruszają się z różnymi prędkościami, jest jednym ze sposobów wykrywania cyklu. Jeśli istnieje cykl, Dwa wskaźniki (powiedzmy p1 i p2) będą wskazywać na ten sam element po skończonej liczbie kroków. Co ciekawe, można udowodnić, że element, przy którym się spotykają, będzie w tej samej odległości od początku pętli (kontynuując przemierzanie listy w tym samym kierunku do przodu), jak początek pętli jest do głowy listy. Oznacza to, że jeśli liniowa część listy zawiera elementy k, Dwa wskaźniki spotkają się wewnątrz pętli o długości m w punkcie m-k Od początku pętli lub elementów k do "końca" pętli (oczywiście jest to pętla, więc ma no ' end '- to tylko 'start' po raz kolejny). I to daje nam sposób na znalezienie początku pętli:

  2. Po wykryciu cyklu pozwól {[3] } pozostać wskazującym na element, w którym pętla dla powyższego kroku zakończyła się, ale zresetuj p1 tak, aby wskazywała z powrotem na początek listy. Teraz przesuń każdy wskaźnik po jednym elemencie na raz. Ponieważ p2 zaczęło się wewnątrz pętli, będzie ona kontynuowana. Po krokach k (równa odległości początku pętli od głowicy z listy), p1 i p2 spotkają się ponownie. Daje to odniesienie do początku pętli.

  3. Teraz można łatwo ustawić p1 (lub p2), aby wskazywały na element rozpoczynający pętlę i przesuwały pętlę, aż p1 zakończy się wskazaniem wstecz na element początkowy. W tym momencie p1 odwołuje się do listy 'last' I następny wskaźnik może być ustawiony na null.


Oto szybki i brudny kod Javy zakładający linkowaną listę Nodes gdzie Node ma next odniesienie. Można to zoptymalizować, ale powinno dać ci podstawową ideę: {]}

Node slow, fast, start;
fast = slow = head;

//PART I - Detect if a loop exists
while (true)
{
    // fast will always fall off the end of the list if it is linear
    if (fast == null || fast.next == null)
    {
        // no loop
        return;
    }
    else if (fast == slow || fast.next == slow)
    {
        // detected a loop
        break;
    }
    else
    {
        fast = fast.next.next; // move 2 nodes at at time
        slow = slow.next; // move 1 node at a time
    }
}

//PART II - Identify the node that is the start of the loop
fast = head; //reset one of the references to head of list

//until both the references are one short of the common element which is the start of the loop
while(fast.next != slow.next) 
{
    fast = fast.next;
    slow = slow.next;
}

start = fast.next;

//PART III - Eliminate the loop by setting the 'next' pointer 
//of the last element to null
fast = start;
while(fast.next != start)
{
    fast = fast.next;
}

fast.next = null; //break the loop

To Wyjaśnienie może pomóc w wyjaśnieniu dlaczego za częścią II:

Załóżmy, że długość cyklu wynosi M, i długości reszty linked list is L. Let ' s figure out jaka jest pozycja w cyklu, gdy pierwsze spotkanie t1/t2?

Zdefiniuj pierwszy węzeł w cyklu pozycja 0, po linkach my mieć pozycję 1, 2,..., do M-1. ( kiedy chodzimy w cyklu, nasz prąd position is (walk_length) mod M, prawda?) Załóżmy, że t1/t2 spotkają się po raz pierwszy w pozycji p, wtedy ich czas podróży jest to samo, (L + k1*M + p) / v = (L + k2 * M+p) / 2v dla niektórych k1

Więc wnioskuje, że jeśli T1 zaczyna się od p, T2 zaczynają się od głowy i poruszają się na tej samej prędkości, wtedy dotacja na spotkanie W POZYCJI 0, pierwszy węzeł cykl. QED.

Więcej referencje:

 58
Author: no.good.at.coding,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:31:59

Rozwiązanie 1 - dzięki uprzejmości Career Cup i książce "Cracking the Coding Interview" :

public static LinkedListNode findStartOfLoop(LinkedListNode head) {
    LinkedListNode n1 = head;
    LinkedListNode n2 = head; 

    // find meeting point using Tortoise and Hare algorithm
    // this is just Floyd's cycle detection algorithm
    while (n2.next != null) { 
        n1 = n1.next; 
        n2 = n2.next.next; 
        if (n1 == n2) { 
            break; 
        }
    }

    // Error check - there is no meeting point, and therefore no loop
    if (n2.next == null) {
        return null;
    }

    /* Move n1 to Head. Keep n2 at Meeting Point.  Each are k steps
    /* from the Loop Start. If they move at the same pace, they must
     * meet at Loop Start. */
    n1 = head; 
    while (n1 != n2) { 
        n1 = n1.next; 
        n2 = n2.next; 
    }
    // Now n2 points to the start of the loop.
    return n2;
}

Wyjaśnienie tego rozwiązania jest prosto z książki:

Jeśli przesuniemy Dwa wskaźniki, jeden z prędkość 1 i drugi z prędkością 2, oni skończy się spotkanie, jeśli połączone lista ma pętlę. Dlaczego? Pomyśl o dwóch samochody jadące po torze; szybszy samochód zawsze będzie mijał wolniejszy!

Najtrudniejszą częścią jest znalezienie początku z pętla. Wyobraź sobie, jako analogię, dwie osoby ścigające się po torze, jeden biegający dwa razy szybciej niż inne. Jeśli zaczną w tym samym miejsce, kiedy się spotkają? Oni następne spotkanie odbędzie się na początku następne okrążenie.

Załóżmy, że Fast Runner miał przewagę k metrów na okrążeniu. Kiedy będą następne spotkać? Spotkają się przed początek następnego okrążenia. (Dlaczego? Szybko Runner zrobiłby k + 2 (n-k) kroki, w tym jego fory, oraz Slow Runner zrobiłby n-k kroki oba będą krokami k przed początek pętli).

Teraz, wracając do problemu, kiedy Fast Runner (n2) i Slow Runner (n1) poruszają się po naszym okrągła lista powiązana, n2 będzie miała początek pętli, gdy n1 wchodzi. W szczególności będzie miał początek K, gdzie k jest liczbą węzłów przed pętlą. Ponieważ n2 ma początek węzłów k, n1 i n2 spotka węzły k przed rozpoczęciem na pętla.

Więc teraz wiemy, co następuje:

  1. Head to węzły k z LoopStart (z definicji)
  2. MeetingPoint dla n1 i n2 to węzły k z Loopstartu (jak pokazano powyżej)

Tak więc, jeśli przesuniemy n1 z powrotem do głowy i utrzymamy n2 na Meetingpoincie, i poruszymy ich oboje w tym samym tempie, spotkają się w LoopStart

Rozwiązanie 2 - dzięki uprzejmości mnie:)

public static LinkedListNode findHeadOfLoop(LinkedListNode head) {

    int indexer = 0;
    Map<LinkedListNode, Integer> map = new IdentityHashMap<LinkedListNode, Integer>();
    map.put(head, indexer);
    indexer++;

    // start walking along the list while putting each node in the HashMap
    // if we come to a node that is already in the list, 
    // then that node is the start of the cycle 
    LinkedListNode curr = head;
    while (curr != null) {

        if (map.containsKey(curr.next)) {
            curr = curr.next;
            break;
        }
        curr = curr.next;
        map.put(curr, indexer);
        indexer++;
    }
    return curr;
}
Mam nadzieję, że to pomoże.
Hristo
 14
Author: Hristo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-10 19:47:05

Ta odpowiedź nie ma na celu konkurowania o odpowiedź, ale raczej Wyjaśnienie nieco więcej na temat spotkania dwóch węzłów w algorytmie żółwia i zająca.

  1. Oba węzły w końcu wejdą w pętlę. Ponieważ jeden porusza się szybciej (F) niż drugi (S), (F) W końcu okrąży (S).

  2. Jeśli początek pętli znajduje się w nagłówku listy, (F) musi się spotkać (s) z powrotem w nagłówku listy. To tylko dlatego, że (F)'S prędkość jest 2X (S)'S; jeśli to było 3X to nie byłoby Bądź prawdziwy. Jest to prawdą ponieważ (F) ukończy jedno okrążenie kiedy (S) ukończy pół okrążenia, więc kiedy (S) ukończy swoje pierwsze okrążenie, (F) ukończy dwa okrążenia - i jest z powrotem na początku pętli z (s).

  3. Jeśli początek pętli nie znajduje się w nagłówku listy, to do czasu (s) wejścia do pętli, (F) miał początek (k) węzłów w pętli. Ponieważ prędkość (S) jest tylko jednym węzłem na raz, spotka (F) Na (K) węzłach od początku pętli - jak w (k) więcej kroków przed osiągnięciem start, a nie (K) kroki po starcie. Nie byłoby to prawdą, gdyby prędkość (s) nie była jedna, a stosunek prędkości nie był 2:1 między (F) I (S).

    3.1. To tutaj robi się trochę trudne do wyjaśnienia. Możemy się zgodzić, że (F) będzie kontynuować docieranie (S) aż w końcu się spotkają (Patrz 1 powyżej), ale dlaczego na (k) węzłach od początku pętli? Rozważmy następujące równanie, gdzie M to liczba węzłów lub odległość pętli, a k to początek (F); równanie przedstawia odległość przejechany przez (F) podany czas t z lewej strony pod względem odległości przejechanej przez (S) z prawej:

    D_F (t) = 2 * d_S (t) + k

    Więc kiedy (S) wchodzi w pętlę i przebył 0 odległość w pętli, (F) przebył tylko (K) odległość. W czasie d_S = M - k, d_F = 2m - k. ponieważ musimy również użyć matematyki modularnej, biorąc pod uwagę, że M reprezentuje całkowitą odległość pojedynczego okrążenia w pętli, pozycja (F) I (S) W dowolnej całości m (bez reszty) wynosi 0. Więc w kategoriach Pozycja (lub różnica), pozostawia k (a raczej-k).

    I tak wreszcie, (S) I (F) spotkają się w pozycji (0 - k), lub (k) węzłów Od początku pętli.

  4. Biorąc pod uwagę [3] powyżej, ponieważ (k) reprezentuje początek (F) i ponieważ (F) przebył 2X odległość (s) przejechaną, aby wejść do pętli od początku listy, (k) reprezentuje również odległość od początku listy, która następnie reprezentuje początek pętli.

It ' s a bit late tutaj, więc mam nadzieję, że udało mi się skutecznie wyartykułować. Daj mi znać inaczej, a postaram się zaktualizować odpowiedź.

 6
Author: bitxwise,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-10 08:27:18

Jeśli użycie mapy hashowej tożsamości (np. IdentityHashMap) jest dozwolone, jest to niezwykle łatwe do rozwiązania. Ale zużywa więcej miejsca.

Przemierzaj listę węzłów. Dla każdego napotkanego węzła dodaj go do mapy tożsamości. Jeśli węzeł już istniał na mapie tożsamości, to lista ma okrągłe łącze i węzeł, który był przed tym konfliktem jest znany (sprawdź przed przeniesieniem lub zachowaj "ostatni węzeł") -- wystarczy ustawić" następny " odpowiednio, aby złamać cykl.

Podążanie za tym prostym podejściem powinno być zabawnym ćwiczeniem: kod jest pozostawiony jako ćwiczenie dla czytelnika.

Szczęśliwe kodowanie.

 5
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-09 20:50:58
 0--->1---->2---->3---->4---->5---->6
                  ▲                 |
                  |                 ▼
                 11<—-22<—-12<—-9<—-8  

Wstaw atrapa węzła za każdym węzłem listy linków i przed wstawieniem sprawdź, czy węzeł obok jest atrapa czy nie. Jeśli next to next jest atrybutem, Wstaw null W next tego węzła.

 0-->D->1-->D->2-->D->3->D-->4->D-->5->D-->6
                     ▲                      |
                  /                         ▼
                 11<—D<-22<—D<-12<—D<-9<—D<--8 


Node(11)->next->next == D
Node(11)->next =null
 3
Author: Parag Bafna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-05 05:59:07
//Find a Loop in Linked List and remove link between node

    public void findLoopInList() {
            Node fastNode = head;
            Node slowNode = head;
            boolean isLoopExist = false;
            while (slowNode != null && fastNode != null && fastNode.next != null) {
                fastNode = fastNode.next.next;
                slowNode = slowNode.next;
                if (slowNode == fastNode) {
                    System.out.print("\n Loop Found");
                    isLoopExist = true;
                    break;
                }
            }
            if (isLoopExist) {
                slowNode = head;
                Node prevNode = null;
                while (slowNode != fastNode) {
                    prevNode = fastNode;
                    fastNode = fastNode.next;
                    slowNode = slowNode.next;
                }
                System.out.print("Loop Found Node : " + slowNode.mData);
                prevNode.next = null; //Remove the Loop
            }
        }

:)GlbMP

 0
Author: Manoj Kumar Pandit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-28 19:13:18