Acredito que criei algo que deve funcionar de maneira geral e eficiente se você não tiver duplicatas * (no entanto, deve ser extensível a qualquer número de furos e a qualquer intervalo de números inteiros).
A idéia por trás desse método é como quicksort, pois encontramos um pivô e o particionamos em volta dele, depois recuamos no (s) lado (s) com um furo. Para ver quais lados têm o furo, encontramos os números mais baixo e mais alto e os comparamos com o pivô e o número de valores desse lado. Digamos que o pivô seja 17 e o número mínimo seja 11. Se não houver furos, deve haver 6 números (11, 12, 13, 14, 15, 16, 17). Se houver 5, sabemos que existe um buraco nesse lado e podemos recuar desse lado para encontrá-lo. Estou tendo problemas para explicar mais claramente do que isso, então vamos dar um exemplo.
15 21 10 13 18 16 22 23 24 20 17 11 25 12 14
Pivô:
10 13 11 12 14 |15| 21 18 16 22 23 24 20 17 25
15 é o pivô, indicado pelos tubos ( ||
). Existem 5 números no lado esquerdo do pivô, como deve haver (15 - 10) e 9 à direita, onde deve haver 10 (25 - 15). Então, nós recursamos no lado direito; notaremos que o limite anterior era 15, caso o buraco esteja adjacente a ele (16).
[15] 18 16 17 20 |21| 22 23 24 25
Agora, existem 4 números no lado esquerdo, mas deve haver 5 (21 - 16). Então, recuamos lá e, novamente, notamos o limite anterior (entre colchetes).
[15] 16 17 |18| 20 [21]
O lado esquerdo tem os 2 números corretos (18 - 16), mas o direito tem 1 em vez de 2 (20 - 18). Dependendo de nossas condições finais, poderíamos comparar o número 1 com os dois lados (18, 20) e ver que 19 está faltando ou se repetir mais uma vez:
[18] |20| [21]
O lado esquerdo tem um tamanho de zero, com um espaço entre o pivô (20) e o limite anterior (18), então 19 é o furo.
*: Se houver duplicatas, você provavelmente poderá usar um conjunto de hash para removê-las em O (N), mantendo o método geral O (N), mas isso pode levar mais tempo do que usar outro método.