Como encontrar o elemento do meio da lista vinculada em uma passagem?

12

Uma das perguntas mais populares sobre estruturas de dados e algoritmos, principalmente feita em entrevista por telefone.

Gilles 'SO- parar de ser mau'
fonte
5
Se as respostas apresentadas neste tópico são o que o entrevistador esperava, essa pergunta não testa a capacidade técnica, mas quão bem o candidato pode se esquivar como um advogado.
G. Bach
2
Essa é uma pergunta terrível para a entrevista, porque depende muito do termo " passe ", que é vago, ambíguo, subjetivo. Quase todas as boas respostas a essa pergunta envolvem abusar da definição para que você possa ignorá-la efetivamente.
precisa saber é o seguinte
2
Bem, a questão levanta muita discussão aqui. Isso significa que é uma boa pergunta de entrevista em um aspecto: isso faz você pensar.
Hendrik Jan
3
Eu so quero responder "ler todos os elementos em um vetor, em seguida, acessar o elemento no tamanho da posição () / 2"
Cort Ammon
1
@HendrikJan Na verdade, acho que é uma pergunta de entrevista completamente terrível. Primeiro, é provável que leve a discussões sobre o que exatamente "de uma só vez" significa, em vez de uma discussão produtiva. Segundo, um candidato poderia descobrir a resposta "correta" e depois rejeitá-la porque pensava que ela violava o critério "de uma só vez". Terceiro, por ser uma pergunta bem conhecida, é um teste melhor de "Você conhece perguntas populares de entrevistas?" do que "Você é um bom candidato para este trabalho?" Qualquer um desses deve ser suficiente para afundar isso como uma pergunta; todos os três simultaneamente são um desastre.
David Richerby

Respostas:

24

Trapaceando e fazendo dois passes ao mesmo tempo, em paralelo. Mas não sei se os recrutadores vão gostar disso.

Pode ser feito em uma única lista vinculada, com um bom truque. Dois ponteiros percorrem a lista, um com velocidade dupla. Quando o jejum chega ao fim, o outro está no meio do caminho.

Hendrik Jan
fonte
1
Sim, não está claro se esse é um passe único. A questão não está clara nesse ponto.
David Richerby
2
A propósito, isso está relacionado ao quebra-cabeça em que você tem duas velas, uma hora acesa cada, e você deve medir 45 minutos.
Hendrik Jan
2
Isso é realmente diferente de iterar a lista, contar elementos e iterar uma segunda vez até o ponto intermediário? Tudo o que é diferente é quando você itera a metade extra. Como o @RBarryYoung menciona outra resposta semelhante, na verdade não é um passe único, é um passe e meio.
2
Se a lista for longa, mover os dois ponteiros "em paralelo" resultará em menos erros de cache do que na iteração desde o início pela segunda vez.
Zwol
2
Isso usa o mesmo princípio que o algoritmo de tartaruga e lebre para detecção de ciclo.
Joshua Taylor
7

Se não for uma lista duplamente vinculada, você pode apenas contar e usar uma lista, mas isso exige duplicar sua memória na pior das hipóteses, e simplesmente não funcionará se a lista for muito grande para armazenar na memória.

Uma solução simples, quase boba, é apenas incrementar o nó do meio a cada dois nós

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;
}
00500005
fonte
Sua segunda opção é a resposta correta (IMHO, é claro).
precisa
1
Na verdade, está fazendo 1 1/2 passes na lista vinculada.
precisa saber é o seguinte
6

Elaborando a resposta de Hendrik

Se for uma lista duplamente vinculada, itere pelas duas extremidades

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

Dado [1, 2, 3] => 2

1, 3
1, 2
2, 2

Dado [1, 2] => 1

1, 2
1, 1

Dado [1] => 1

Dado [] => null

00500005
fonte
Como isso é eficiente? Você também está iterando n vezes e não n / 2.
Karan Khanna
3

Crie uma estrutura com um ponteiro capaz de apontar para nós da lista vinculada e com uma variável inteira que mantenha a contagem do número de nós na lista.

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

start.ptrstart.count=1
start.count

start.count

Prateek
fonte
-2

Crie uma matriz dinâmica, na qual cada elemento da matriz é um ponteiro para cada nó da lista em ordem de deslocamento, começando desde o início. Crie um número inteiro, inicializado como 1, que monitore quantos nós você visitou (o que aumenta cada vez que você acessa um novo nó). Quando você chega ao final, sabe o tamanho da lista e tem uma matriz ordenada de ponteiros para cada nó. Por fim, divida o tamanho da lista por 2 (e subtraia 1 para indexação baseada em 0) e busque o ponteiro mantido nesse índice da matriz; se o tamanho da lista for ímpar, você poderá escolher qual elemento retornar (ainda retornarei o primeiro).

Aqui está um código Java que esclarece o assunto (mesmo que a idéia de uma matriz dinâmica seja um pouco instável). Eu forneceria C / C ++, mas estou muito enferrujado nessa área.

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];

}
Andonaeus
fonte
-3

A recursão é considerada mais de uma passagem?

Percorra a lista até o final, passando uma contagem de números inteiros por referência. Faça uma cópia local desse valor em cada nível para referência posterior e aumente a contagem de ref para a próxima chamada.

No último nó, divida a contagem por dois e trunque / floor () o resultado (se você quiser que o primeiro nó seja o "meio" quando houver apenas dois elementos) ou arredonde para cima (se você quiser que o segundo nó seja o meio"). Use um índice baseado em zero ou um adequadamente.

Desenrolando, combine a contagem de ref com a cópia local (que é o número do nó). Se igual, retorne esse nó; caso contrário, retorne o nó retornado da chamada recursiva.
.

Existem outras maneiras de fazer isso; alguns deles podem ser menos pesados ​​(eu pensei ter visto alguém dizer lê-lo em uma matriz e usar o comprimento da matriz para determinar os parabéns do meio). Mas, francamente, não há boas respostas, porque é uma pergunta estúpida para a entrevista. Número um, que ainda usa listas vinculadas ( opinião de apoio ); Segundo, encontrar o nó do meio é um exercício acadêmico arbitrário, sem valor nos cenários da vida real; Terceiro, se eu realmente precisasse conhecer o nó do meio, minha lista vinculada exporia uma contagem de nós. É mais fácil manter essa propriedade do que perder tempo percorrendo a lista inteira toda vez que eu quero o nó do meio. E finalmente, quatro, todo entrevistador vai gostar ou rejeitar respostas diferentes - o que um entrevistador acha que é liso, outro chamará de ridículo.

Quase sempre respondo às perguntas da entrevista com mais perguntas. Se eu receber uma pergunta como essa (nunca a tenho), pergunto: (1) O que você está armazenando nesta lista vinculada e existe uma estrutura mais apropriada para acessar com eficiência o nó do meio, se houver realmente uma necessidade? ; (2) Quais são minhas restrições? Eu posso torná-lo mais rápido se a memória não for um problema (por exemplo, a resposta da matriz), mas se o entrevistador achar que aumentar a memória é um desperdício, eu vou me apegar. (3) Em que idioma eu vou estar desenvolvendo? Quase todas as linguagens modernas que conheço possuem classes integradas para lidar com listas vinculadas que tornam desnecessária a passagem da lista - por que reinventar algo que foi ajustado para eficiência pelos desenvolvedores da linguagem?

James K
fonte
6
Você não sabe quem votou contra o quê, e sua conclusão de que uma pessoa votou contra tudo pode ou não ser verdade, mas certamente não tem base nos fatos disponíveis para você. Mas estou votando negativamente na sua resposta, porque estamos procurando explicações, não pilhas de código.
David Richerby
Pode ser uma suposição, mas quando olho um momento e menos de 30 segundos depois, cada post tem exatamente -1, não é irracional. Mesmo que fossem 5 ou 6 pessoas diferentes, nenhuma delas deixou um comentário. Mas obrigado por pelo menos indicar uma razão. Não entendo por que uma explicação detalhada é melhor que o código - sim, é para uma entrevista telefônica, mas não estou dando ao OP uma resposta enlatada para regurgitar, estou mostrando a ele uma maneira de fazê-lo. Ou seja, acho que a votação de um post é desnecessária, porque ele tem código desnecessário, mas obrigado por pelo menos dizer o porquê.
James K
Ponto justo - não me ocorreu que você pudesse saber o momento dos votos (ter a página carregada enquanto eles aconteciam é, eu acho, a única maneira pela qual usuários comuns como nós poderiam descobrir isso). Temos algumas metatags sobre por que tentamos evitar o código real aqui: (1) (2) .
David Richerby
Obrigado pelos meta links. Eu li o FAQ, mas não vi nada lá - não que eu tivesse notado algo assim, muito provavelmente, não algo que eu esperaria. Mas também não consegui encontrar nada quando verifiquei novamente depois de ser enganado. Eu ainda posso estar ignorando, mas eu olhei. O raciocínio nos meta posts faz sentido; Obrigado pela resposta.
James K
-5

Usando 2 ponteiros. Incremente um em cada iteração e outro a cada segunda iteração. Quando o primeiro ponteiro aponta para o final da lista vinculada, o segundo ponteiro aponta para o modo intermediário da lista vinculada.

b dharuni
fonte
Isso apenas duplica a resposta de Hendrick . Por favor, não responda, a menos que você tenha algo novo a dizer.
David Richerby