Encontre o elemento do meio de uma lista vinculada

Encontre o elemento do meio de uma lista vinculada

1. Visão geral

Neste tutorial, vamos explicar como encontrar o elemento do meio de uma lista vinculada em Java.

Apresentaremos os principais problemas nas próximas seções e mostraremos diferentes abordagens para resolvê-los.

2. Acompanhando o tamanho

Este problema pode ser facilmente resolvido apenas porkeeping track of the size when we add new elements to the list. Se sabemos o tamanho, também sabemos onde está o elemento do meio, portanto a solução é trivial.

Vejamos um exemplo usando a implementação Java de umLinkedList:

public static Optional findMiddleElementLinkedList(
  LinkedList linkedList) {
    if (linkedList == null || linkedList.isEmpty()) {
        return Optional.empty();
    }

    return Optional.of(linkedList.get(
      (linkedList.size() - 1) / 2));
}

Se verificarmos o código interno da classeLinkedList, podemos ver que, neste exemplo, estamos apenas percorrendo a lista até chegarmos ao elemento do meio:

Node node(int index) {
    if (index < (size >> 1)) {
        Node x = first;
        for (int i = 0; i < index; i++) {
            x = x.next;
        }
        return x;
    } else {
        Node x = last;
        for (int i = size - 1; i > index; i--) {
            x = x.prev;
        }
        return x;
    }
}

3. Encontrando o meio sem saber o tamanho

É muito comum encontrarmos problemas ondewe only have the head node of a linked list,e precisamos encontrar o elemento do meio. Nesse caso, não sabemos o tamanho da lista, o que torna esse problema mais difícil de resolver.

Mostraremos nas próximas seções várias abordagens para resolver este problema, mas primeiro, precisamos criar uma classe para representar um nó da lista.

Vamos criar uma classeNode, que armazena os valoresString:

public static class Node {

    private Node next;
    private String data;

    // constructors/getters/setters

    public boolean hasNext() {
        return next != null;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public String toString() {
        return this.data;
    }
}

Além disso, usaremos este método auxiliar em nossos casos de teste para criar uma lista unida usando apenas nossos nós:

private static Node createNodesList(int n) {
    Node head = new Node("1");
    Node current = head;

    for (int i = 2; i <= n; i++) {
        Node newNode = new Node(String.valueOf(i));
        current.setNext(newNode);
        current = newNode;
    }

    return head;
}

3.1. Encontrar o tamanho primeiro

A abordagem mais simples para resolver esse problema é encontrar o tamanho da lista primeiro e depois seguir a mesma abordagem que usamos anteriormente - para iterar até o elemento do meio.

Vamos ver esta solução em ação:

public static Optional findMiddleElementFromHead(Node head) {
    if (head == null) {
        return Optional.empty();
    }

    // calculate the size of the list
    Node current = head;
    int size = 1;
    while (current.hasNext()) {
        current = current.next();
        size++;
    }

    // iterate till the middle element
    current = head;
    for (int i = 0; i < (size - 1) / 2; i++) {
        current = current.next();
    }

    return Optional.of(current.data());
}

Como podemos ver,this code iterates through the list twice. Therefore, this solution has a poor performance and it’s not recommended.

3.2. Encontrando o Elemento do Meio em Uma Passagem Iterativamente

Agora vamos melhorar a solução anterior, encontrando o elemento do meio com apenas uma iteração na lista.

Para fazer isso iterativamente, precisamos de dois ponteiros para percorrer a lista ao mesmo tempo. One pointer will advance 2 nodes in each iteration, and the other pointer will advance only one node per iteration.

Quando o ponteiro mais rápido chega ao final da lista, o ponteiro mais lento fica no meio:

public static Optional findMiddleElementFromHead1PassIteratively(Node head) {
    if (head == null) {
        return Optional.empty();
    }

    Node slowPointer = head;
    Node fastPointer = head;

    while (fastPointer.hasNext() && fastPointer.next().hasNext()) {
        fastPointer = fastPointer.next().next();
        slowPointer = slowPointer.next();
    }

    return Optional.ofNullable(slowPointer.data());
}

Podemos testar esta solução com um teste de unidade simples, usando listas com número ímpar e par de elementos:

@Test
public void whenFindingMiddleFromHead1PassIteratively_thenMiddleFound() {

    assertEquals("3", MiddleElementLookup
      .findMiddleElementFromHead1PassIteratively(
        createNodesList(5)).get());
    assertEquals("2", MiddleElementLookup
      .findMiddleElementFromHead1PassIteratively(
        reateNodesList(4)).get());
}

3.3. Encontrar o elemento do meio em uma passagem recursivamente

Outra maneira de resolver esse problema em uma passagem é usando a recursão. Podemos iterar até o final da lista para saber o tamanho e,in the callbacks, we just count until the half of the size.

Para fazer isso em Java, vamos criar uma classe auxiliar para manter as referências do tamanho da lista e do elemento do meio durante a execução de todas as chamadas recursivas:

private static class MiddleAuxRecursion {
    Node middle;
    int length = 0;
}

Agora, vamos implementar o método recursivo:

private static void findMiddleRecursively(
  Node node, MiddleAuxRecursion middleAux) {
    if (node == null) {
        // reached the end
        middleAux.length = middleAux.length / 2;
        return;
    }
    middleAux.length++;
    findMiddleRecursively(node.next(), middleAux);

    if (middleAux.length == 0) {
        // found the middle
        middleAux.middle = node;
    }

    middleAux.length--;
}

E, finalmente, vamos criar um método que chama o recursivo:

public static Optional findMiddleElementFromHead1PassRecursively(Node head) {

    if (head == null) {
        return Optional.empty();
    }

    MiddleAuxRecursion middleAux = new MiddleAuxRecursion();
    findMiddleRecursively(head, middleAux);
    return Optional.of(middleAux.middle.data());
}

Novamente, podemos testá-lo da mesma maneira que fizemos antes:

@Test
public void whenFindingMiddleFromHead1PassRecursively_thenMiddleFound() {
    assertEquals("3", MiddleElementLookup
      .findMiddleElementFromHead1PassRecursively(
        createNodesList(5)).get());
    assertEquals("2", MiddleElementLookup
      .findMiddleElementFromHead1PassRecursively(
        createNodesList(4)).get());
}

4. Conclusão

Neste artigo, apresentamos o problema de encontrar o elemento do meio de uma lista vinculada em Java e mostramos diferentes maneiras de resolvê-lo.

Começamos com a abordagem mais simples, onde controlamos o tamanho e, depois disso, continuamos com as soluções para encontrar o elemento do meio do nó principal da lista.

Como sempre, o código-fonte completo dos exemplos está disponívelover on GitHub.