Come trovare l'elemento intermedio dell'elenco collegato in un passaggio?


12

Una delle domande più popolari delle strutture dati e dell'algoritmo, posta principalmente sull'intervista telefonica.


5
Se le risposte presentate in questo thread sono quelle che l'intervistatore si aspettava, allora questa domanda non mette alla prova l'abilità tecnica, ma quanto bene il candidato può schivare come un avvocato.
G. Bach,

2
Questa è una terribile domanda di intervista perché si basa in modo critico sul termine " passare " che è vago, ambiguo, soggettivo. Quasi tutte le risposte valide a questa domanda implicano l'abuso della definizione in modo da poterla ignorare efficacemente.
RBarryYoung,

2
Bene, la domanda solleva molte discussioni qui. Ciò significa che è una buona domanda di intervista sotto un aspetto: ti fa iniziare a pensare.
Hendrik Jan,

3
Voglio così rispondere "leggi tutti gli elementi in un vettore, quindi accedi all'elemento in posizione size () / 2"
Cort Ammon,

1
@HendrikJan In realtà, penso che sia una domanda di intervista completamente terribile. In primo luogo, è probabile che conduca a discussioni su cosa significhi esattamente "in un passaggio", piuttosto che una discussione produttiva. In secondo luogo, un candidato potrebbe capire la risposta "corretta" e poi respingerla perché pensava che violasse il criterio "in un passaggio". In terzo luogo, poiché è una domanda ben nota, è un test migliore di "Conosci domande di intervista popolari?" di "Sei adatto per questo lavoro?" Ognuno di questi dovrebbe essere sufficiente per affondare questo come una domanda; tutti e tre contemporaneamente sono un disastro.
David Richerby,

Risposte:


24

Imbrogliando e facendo due passaggi contemporaneamente, in parallelo. Ma non so se ai reclutatori piacerà questo.

Può essere fatto su un unico elenco collegato, con un bel trucco. Due puntatori viaggiano sull'elenco, uno a doppia velocità. Quando quello veloce raggiunge la fine, l'altro è a metà strada.


1
Sì, non è chiaro se si tratti di un singolo passaggio. La domanda non è chiara su questo punto.
David Richerby,

2
A proposito, questo è legato al puzzle in cui hai due candele, un'ora bruciante ciascuna, e ti viene chiesto di misurare 45 minuti.
Hendrik Jan,

2
È davvero diverso quindi iterare l'elenco, contare gli elementi e quindi iterare una seconda volta fino a metà? Tutto ciò che è diverso è quando si itera la metà extra. Come @RBarryYoung menziona sull'altra risposta simile, in realtà non è un singolo passaggio, è un passaggio e mezzo.

2
Se l'elenco è lungo, lo spostamento di entrambi i puntatori "in parallelo" comporterà un minor numero di errori nella cache rispetto all'iterazione dall'inizio una seconda volta.
zwol,

2
Questo utilizza lo stesso principio dell'algoritmo di tartaruga e lepre per il rilevamento del ciclo.
Joshua Taylor,

7

Se non è un elenco doppiamente collegato, potresti semplicemente contare e utilizzare un elenco, ma ciò richiede il raddoppio della memoria nel caso peggiore e semplicemente non funzionerà se l'elenco è troppo grande per essere memorizzato.

Una soluzione semplice, quasi sciocca, è semplicemente incrementare il nodo centrale ogni due nodi

function middle(start) {
    var middle = start
    var nextnode = start
    var do_increment = false;
    while (nextnode.next != null) {
        if (do_increment) {
             middle = middle.next;
        }
        do_increment = !do_increment;
        nextnode = nextnode.next;
    }
    return middle;
}

La tua seconda opzione è la risposta corretta (IMHO ovviamente).
Matthew Crumley,

1
In realtà sta facendo 1 1/2 passaggi sull'elenco collegato.
RBarryYoung,

6

Elaborazione della risposta di Hendrik

Se è un elenco doppiamente collegato, scorrere da entrambe le estremità

function middle(start, end) {
  do_advance_start = false;
  while(start !== end && start && end) {
     if (do_advance_start) {
        start = start.next
     }
     else {
        end = end.prev
     }
     do_advance_start = !do_advance_start
  }
  return (start === end) ? start : null;
}

Dato [1, 2, 3] => 2

1, 3
1, 2
2, 2

Dato [1, 2] => 1

1, 2
1, 1

Dato [1] => 1

Dato [] => null


Come è efficiente? Stai anche ripetendo n volte e non n / 2.
Karan Khanna,

3

Creare una struttura con un puntatore in grado di puntare a nodi dell'elenco collegato e con una variabile intera che tenga conto del numero di nodi nell'elenco.

struct LL{
    struct node *ptr;
    int         count;
}start;

start.ptrstart.count=1
start.count

start.count


-2

Creare un array dinamico, in cui ogni elemento dell'array è un puntatore a ciascun nodo nell'elenco in ordine di movimento, a partire dall'inizio. Crea un numero intero, inizializzato su 1, che tenga traccia di quanti nodi hai visitato (che aumenta ogni volta che vai su un nuovo nodo). Quando arrivi alla fine, sai quanto è grande l'elenco e hai un array ordinato di puntatori per ciascun nodo. Infine, dividi la dimensione dell'elenco per 2 (e sottrai 1 per l'indicizzazione basata su 0) e recupera il puntatore contenuto in quell'indice dell'array; se la dimensione dell'elenco è dispari, puoi scegliere quale elemento restituire (restituirò comunque il primo).

Ecco un po 'di codice Java che fa capire il punto (anche se l'idea di un array dinamico sarà un po' traballante). Fornirei C / C ++ ma sono molto arrugginito in quella zona.

public Node getMiddleNode(List<Node> nodes){

    int size = 1;
    //add code to dynamically increase size if at capacity after adding
    Node[] pointers = new Node[10];

    for (int i = 0; i < nodes.size(); i++){
        //remember to dynamically allocate more space if needed
        pointers[i] = nodes.get(i);
        size++;
    }

    return pointers[(size - 1)/2];

}

-3

La ricorsione è considerata più di un passaggio?

Attraversa l'elenco fino alla fine, passando un numero intero per riferimento. Crea una copia locale di quel valore a ciascun livello per riferimento futuro e incrementa il conteggio di riferimento andando alla chiamata successiva.

Nell'ultimo nodo, dividere il conteggio per due e troncare / floor () il risultato (se si desidera che il primo nodo sia "centrale" quando sono presenti solo due elementi) o arrotondare per eccesso (se si desidera che il secondo nodo sia la metà"). Utilizzare un indice a base zero o uno in modo appropriato.

Svolgendo, abbina il conteggio di riferimento alla copia locale (che è il numero del nodo). Se uguale, restituisce quel nodo; else restituisce il nodo restituito dalla chiamata ricorsiva.
.

Ci sono altri modi per farlo; alcuni potrebbero essere meno ingombranti (pensavo di aver visto qualcuno dire leggerlo in un array e usare la lunghezza dell'array per determinare i kudos di mezzo). Ma francamente, non ci sono buone risposte, perché è una domanda stupida da intervista. Numero uno, che utilizza ancora elenchi collegati ( parere di supporto ); Secondo, trovare il nodo centrale è un esercizio accademico arbitrario senza valore negli scenari della vita reale; Tre, se avessi davvero bisogno di conoscere il nodo centrale, la mia lista collegata avrebbe rivelato il conteggio dei nodi. È molto più facile mantenere quella proprietà che perdere tempo attraversando l'intero elenco ogni volta che voglio il nodo centrale. E infine, quattro, a ogni intervistatore piacerà o rifiuterà risposte diverse: ciò che un intervistatore ritiene slick, un altro lo chiamerà ridicolo.

Quasi sempre rispondo alle domande dell'intervista con più domande. Se ricevo una domanda come questa (non l'ho mai fatto), chiederei (1) Cosa stai memorizzando in questo elenco collegato ed esiste una struttura più appropriata per accedere in modo efficiente al nodo centrale se è davvero necessario farlo ; (2) Quali sono i miei vincoli? Posso renderlo più veloce se la memoria non è un problema (ad es. La risposta dell'array), ma se l'intervistatore ritiene che caricare la memoria sia dispendioso, mi verrà oscurato. (3) In che lingua svilupperò? Quasi ogni linguaggio moderno che conosco ha classi integrate per gestire elenchi collegati che rendono superfluo attraversare l'elenco: perché reinventare qualcosa che è stato messo a punto per l'efficienza dagli sviluppatori del linguaggio?


6
Non sai chi ha votato a fondo, quindi la tua conclusione che una persona ha votato a fondo tutto può essere o non essere vera, ma di certo non ha basi nei fatti a tua disposizione. Ma sto ridimensionando la tua risposta perché stiamo cercando spiegazioni, non pile di codice.
David Richerby,

Potrebbe essere un presupposto, ma quando guardo un momento e meno di 30 secondi dopo ogni post ha esattamente -1, non è irragionevole. Anche se erano 5 o 6 persone diverse, nessuno di loro ha lasciato un commento sul perché. Ma grazie almeno per aver indicato un motivo. Non capisco perché una spiegazione prolissa sia migliore del codice: sì, è per un'intervista telefonica, ma non sto dando all'OP una risposta fissa per rigurgitare, gli sto mostrando un modo per farlo. IE Penso che il downvoting di un post sia privo di codice, ma grazie per avermi detto almeno perché lo hai fatto.
James K,

Giusto punto: non mi era mai venuto in mente che tu potessi conoscere il momento dei voti (avere la pagina caricata mentre si sono verificati è, credo, l'unico modo in cui gli utenti ordinari come noi potevano scoprirlo). Abbiamo un paio di meta post sul perché cerchiamo di evitare il codice reale qui: (1) (2) .
David Richerby,

Grazie per i meta link. Ho letto le FAQ, ma non ho visto nulla lì - non che avrei notato qualcosa del genere, molto probabilmente, non qualcosa che mi sarei aspettato. Ma non sono riuscito a trovare nulla quando ho ricontrollato dopo essere stato oscurato. Potrei ancora trascurarlo, ma ho guardato. Il ragionamento nei meta post ha senso; Grazie per la risposta.
James K,

-5

Usando 2 puntatori. Incrementa uno ad ogni iterazione e l'altro ad ogni seconda iterazione. Quando il 1 ° puntatore punta alla fine dell'elenco collegato, il 2 ° puntatore punta alla modalità centrale dell'elenco collegato.


Questo duplica solo la risposta di Hendrick . Per favore, non rispondere a meno che tu non abbia qualcosa di nuovo da dire.
David Richerby,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.