Une question d’entrevue intéressante qu’un de mes collègues utilise:
Supposons que vous receviez une très longue liste non triée d'entiers non signés 64 bits. Comment pourriez-vous trouver le plus petit entier non négatif qui ne se trouve pas n'apparaisse pas dans la liste?
SUIVI: Maintenant que la solution évidente par le tri a été proposée, pouvez-vous le faire plus rapidement que O (n log n)?
SUIVI: Votre algorithme doit être exécuté sur un ordinateur avec, par exemple, 1 Go de mémoire.
CLARIFICATION: La liste est en RAM, même si elle risque d’en consommer beaucoup. On vous donne la taille de la liste, disons N, à l'avance.
Si la structure de données peut être mutée sur place et prend en charge l'accès aléatoire, vous pouvez le faire en O(N) heure et en O(1) espace supplémentaire. Parcourez simplement le tableau séquentiellement et pour chaque index, écrivez la valeur de l'index dans l'index spécifié par valeur, en plaçant récursivement toute valeur de cet emplacement à sa place et en jetant les valeurs> N. Repartez ensuite dans le tableau à la recherche du point où valeur ne correspond pas à l'index - c'est la plus petite valeur qui ne figure pas dans le tableau. Il en résulte au plus 3N comparaisons et n’utilise que quelques valeurs d’espace temporaire.
# Pass 1, move every value to the position of its value
for cursor in range(N):
target = array[cursor]
while target < N and target != array[target]:
new_target = array[target]
array[target] = target
target = new_target
# Pass 2, find first location where the index doesn't match the value
for cursor in range(N):
if array[cursor] != cursor:
return cursor
return N
Voici une solution simple O(N)
qui utilise O(N)
space. Je suppose que nous limitons la liste d'entrées aux nombres non négatifs et que nous voulons trouver le premier nombre non négatif qui ne figure pas dans la liste.
N
.N
booleans, initialisé à tous false
. X
dans la liste, si X
est inférieur à N
, définissez l'élément X'th
du tableau sur true
.0
, en recherchant le premier élément false
. Si vous trouvez la première false
à l'index I
, alors I
est la réponse. Autrement (c.-à-d. Lorsque tous les éléments sont true
), la réponse est N
.En pratique, le "tableau de N
booleans" serait probablement codé sous la forme d'un "bitmap" ou d'un "jeu de bits" représenté par un tableau byte
ou int
. Cela utilise généralement moins d’espace (selon le langage de programmation) et permet d’analyser plus rapidement la première false
.
Voici comment/pourquoi l’algorithme fonctionne.
Supposons que les nombres N
de la liste ne soient pas distincts ou qu’un ou plusieurs d’entre eux soient supérieurs à N
. Cela signifie qu'il doit y avoir au moins un nombre dans la plage 0 .. N - 1
qui ne figure pas dans la liste. Donc, le problème de trouver le plus petit nombre manquant doit donc se réduire au problème de trouver le plus petit nombre manquant moins que N
. Cela signifie que nous n'avons pas besoin de garder trace des nombres supérieurs ou égaux à N
... car ils ne seront pas la solution.
L'alternative au paragraphe précédent est que la liste est une permutation des nombres de 0 .. N - 1
. Dans ce cas, l'étape 3 définit tous les éléments du tableau sur true
et l'étape 4 nous indique que le premier nombre "manquant" est N
.
La complexité de calcul de l'algorithme est O(N)
avec une constante de proportionnalité relativement petite. Il effectue deux passages linéaires dans la liste ou un seul passage si la longueur de la liste est connue pour commencer. Il n'est pas nécessaire de représenter en mémoire la totalité de la liste. Par conséquent, l'utilisation de la mémoire asymptotique de l'algorithme est exactement ce qui est nécessaire pour représenter le tableau de booléens. c'est-à-dire O(N)
bits.
(En revanche, les algorithmes qui reposent sur le tri ou le partitionnement en mémoire supposent que vous puissiez représenter la liste entière en mémoire. Sous la forme posée, la question aurait nécessité des mots O(N)
de 64 bits.)
@Jorn commente que les étapes 1 à 3 sont une variante du comptage. En un sens, il a raison, mais les différences sont significatives:
Xmax - Xmin
, Xmax
étant le plus grand nombre de la liste et Xmin
, le plus petit nombre de la liste. Chaque compteur doit pouvoir représenter N états; c'est-à-dire en supposant une représentation binaire, il doit avoir un type entier (au moins) ceiling(log2(N))
bits.Xmax
et Xmin
.ceiling(log2(N)) * (Xmax - Xmin)
bits.En revanche, l'algorithme présenté ci-dessus requiert simplement N
bits dans le pire et le meilleur des cas.
Cependant, cette analyse conduit à l’intuition que si l’algorithme effectuait un premier passage dans la liste en recherchant un zéro (et en comptant le nombre d’éléments de la liste si nécessaire), il donnerait une réponse plus rapide sans espace du tout s’il trouve le zéro. Cela vaut certainement la peine s'il existe une forte probabilité de trouver au moins un zéro dans la liste. Et cette passe supplémentaire ne change pas la complexité globale.
EDIT: J'ai changé la description de l'algorithme pour utiliser "tableau de booléens" car les gens ont apparemment trouvé ma description originale en utilisant des bits et des bitmaps pour être déroutant.
Étant donné que le PO a maintenant spécifié que la liste d'origine est conservée dans RAM et que l'ordinateur ne dispose que de 1 Go de mémoire, je vais aller de l'avant et prédire que la réponse est zéro.
1 Go de RAM signifie que la liste peut contenir au maximum 134 217 728 numéros. Mais il y a 264 = 18 446 744 073 709 551 616 nombres possibles. La probabilité que zéro soit dans la liste est donc de 1 sur 137 438 953 472.
En revanche, mes chances d'être frappées par la foudre cette année sont de 1 sur 700 000. Et mes chances de { se faire toucher par une météorite } sont d'environ 1 billion de dollars. Donc, je suis environ dix fois plus susceptible d’être écrit dans un journal scientifique en raison de ma mort prématurée par un objet céleste que la réponse n’étant pas nulle.
Comme indiqué dans d'autres réponses, vous pouvez effectuer une sorte, puis simplement numériser jusqu'à trouver un écart.
Vous pouvez améliorer la complexité algorithmique de O(N) et conserver un espace O(N) en utilisant un QuickSort modifié, dans lequel vous éliminez les partitions qui ne sont pas des candidats potentiels pour contenir l'espace.
Cela économise un grand nombre de calculs.
Pour illustrer l’un des pièges de la pensée O(N)
, voici un algorithme O(N)
qui utilise l’espace O(1)
.
for i in [0..2^64):
if i not in list: return i
print "no 64-bit integers are missing"
Puisque les nombres ont tous une longueur de 64 bits, nous pouvons utiliser tri de base sur eux, qui est O (n). Triez-les, puis scannez-les jusqu'à ce que vous trouviez ce que vous cherchez.
si le plus petit nombre est zéro, effectuez un balayage avant jusqu'à ce que vous trouviez un espace. Si le plus petit nombre n'est pas zéro, la réponse est zéro.
Pour une méthode efficace en termes d'espace et si toutes les valeurs sont distinctes, vous pouvez le faire dans l'espace O( k )
et le temps O( k*log(N)*N )
. C'est peu encombrant, il n'y a pas de données en mouvement et toutes les opérations sont élémentaires (ajout de soustraction).
U = N; L=0
k
. Comme ça:0->(1/k)*(U-L) + L
, 0->(2/k)*(U-L) + L
, 0->(3/k)*(U-L) + L
... 0->(U-L) + L
count{i}
) sont dans chaque région. (N*k
étapes)h
) qui n'est pas complète. Cela signifie count{h} < upper_limit{h}
. (k
étapes)h - count{h-1} = 1
vous avez votre réponseU = count{h}; L = count{h-1}
cela peut être amélioré en utilisant le hachage (merci à Nic cette idée).
k
. Comme ça:L + (i/k)->L + (i+1/k)*(U-L)
inc count{j}
en utilisant j = (number - L)/k
(if L < number < U)
h
) qui ne contient pas k élémentscount{h} = 1
h est votre réponseU = maximum value in region h
L = minimum value in region h
Cela fonctionnera dans O(log(N)*N)
.
Il suffit de les trier, puis de parcourir la séquence jusqu'à ce que je trouve un écart (y compris l'écart au début entre zéro et le premier nombre).
En termes d'algorithme, quelque chose comme ceci le ferait:
def smallest_not_in_list(list):
sort(list)
if list[0] != 0:
return 0
for i = 1 to list.last:
if list[i] != list[i-1] + 1:
return list[i-1] + 1
if list[list.last] == 2^64 - 1:
assert ("No gaps")
return list[list.last] + 1
Bien sûr, si vous avez beaucoup plus de mémoire que CPU Grunt, vous pouvez créer un masque de bits de toutes les valeurs 64 bits possibles et simplement définir les bits pour chaque nombre de la liste. Recherchez ensuite le premier bit 0 dans ce masque de bits. Cela en fait une opération O(n) en terme de temps mais sacrément chère en termes de mémoire requise :-)
Je doute que vous puissiez améliorer O(n), car je ne vois pas comment le faire, mais ne pas examiner chaque chiffre au moins une fois.
L'algorithme pour celui-ci serait le suivant:
def smallest_not_in_list(list):
bitmask = mask_make(2^64) // might take a while :-)
mask_clear_all (bitmask)
for i = 1 to list.last:
mask_set (bitmask, list[i])
for i = 0 to 2^64 - 1:
if mask_is_clear (bitmask, i):
return i
assert ("No gaps")
Triez la liste, examinez les premier et deuxième éléments et commencez à monter jusqu'à ce qu'il y ait un vide.
Eh bien, s'il n'y a qu'un seul nombre manquant dans une liste de nombres, le moyen le plus simple de rechercher le nombre manquant consiste à additionner la série et à soustraire chaque valeur de la liste. La valeur finale est le nombre manquant.
Merci à egon, swilden et à Stephen C pour mon inspiration. Premièrement, nous connaissons les limites de la valeur de l'objectif car elle ne peut pas être supérieure à la taille de la liste. En outre, une liste de 1 Go peut contenir au plus 134217728 (128 * 2 ^ 20) entiers 64 bits.
Partie de hachage
Je propose d’utiliser le hachage pour réduire considérablement notre espace de recherche. Tout d'abord, racine carrée la taille de la liste. Pour une liste de 1 Go, cela correspond à N = 11 586. Configurez un tableau entier de taille N. Parcourez la liste et prenez la racine carrée * de chaque nombre que vous trouvez comme valeur de hachage. Dans votre table de hachage, incrémentez le compteur pour ce hachage. Ensuite, parcourez votre table de hachage. Le premier compartiment que vous trouvez et qui n’est pas égal à sa taille maximale définit votre nouvel espace de recherche.
Partie bitmap
Configurez maintenant une mappe de bits régulière égale à la taille de votre nouvel espace de recherche, puis parcourez à nouveau la liste des sources en remplissant la bitmap lorsque vous trouvez chaque numéro dans votre espace de recherche. Lorsque vous avez terminé, le premier bit non défini de votre bitmap vous donnera votre réponse.
Cette opération sera effectuée dans le temps O(n) et dans l'espace O(sqrt(n)).
(* Vous pouvez utiliser quelque chose comme transfert de bits pour le faire beaucoup plus efficacement et simplement faire varier le nombre et la taille des compartiments en conséquence.)
Voici ma réponse écrite en Java:
Idée de base: 1- Parcourez le tableau en jetant les nombres positifs, zéros et négatifs en double tout en faisant la somme, en obtenant également le nombre positif maximal et en conservant les nombres positifs uniques dans une carte.
2- Calculez la somme sous la forme max * (max + 1)/2.
3- Trouvez la différence entre les sommes calculées aux étapes 1 et 2
4- Bouclez de nouveau de 1 au minimum de [différence de somme, max] et renvoyez le premier nombre qui ne figure pas sur la carte renseignée à l'étape 1.
public static int solution(int[] A) {
if (A == null || A.length == 0) {
throw new IllegalArgumentException();
}
int sum = 0;
Map<Integer, Boolean> uniqueNumbers = new HashMap<Integer, Boolean>();
int max = A[0];
for (int i = 0; i < A.length; i++) {
if(A[i] < 0) {
continue;
}
if(uniqueNumbers.get(A[i]) != null) {
continue;
}
if (A[i] > max) {
max = A[i];
}
uniqueNumbers.put(A[i], true);
sum += A[i];
}
int completeSum = (max * (max + 1)) / 2;
for(int j = 1; j <= Math.min((completeSum - sum), max); j++) {
if(uniqueNumbers.get(j) == null) { //O(1)
return j;
}
}
//All negative case
if(uniqueNumbers.isEmpty()) {
return 1;
}
return 0;
}
Nous pourrions utiliser une table de hachage pour contenir les nombres. Une fois que tous les nombres sont terminés, lancez un compteur de 0 jusqu'à trouver le plus bas. Un hachage raisonnablement bon hachera et stockera en temps constant et récupérera en temps constant.
for every i in X // One scan Θ(1)
hashtable.put(i, i); // O(1)
low = 0;
while (hashtable.get(i) <> null) // at most n+1 times
low++;
print low;
Dans le pire des cas, s'il y a des éléments n
dans le tableau et que c'est {0, 1, ... n-1}
, auquel cas, la réponse sera obtenue à n
, tout en le conservant O(n)
.
int i = 0;
while ( i < Array.Length)
{
if (Array[i] == i + 1)
{
i++;
}
if (i < Array.Length)
{
if (Array[i] <= Array.Length)
{//SWap
int temp = Array[i];
int AnoTemp = Array[temp - 1];
Array[temp - 1] = temp;
Array[i] = AnoTemp;
}
else
i++;
}
}
for (int j = 0; j < Array.Length; j++)
{
if (Array[j] > Array.Length)
{
Console.WriteLine(j + 1);
j = Array.Length;
}
else
if (j == Array.Length - 1)
Console.WriteLine("Not Found !!");
}
}
Vous pouvez le faire en O(n) heure et en O(1) espace supplémentaire, bien que le facteur caché soit assez grand. Ce n'est pas une façon pratique de résoudre le problème, mais cela pourrait néanmoins être intéressant.
Pour chaque entier non signé de 64 bits (dans l'ordre croissant), parcourez la liste jusqu'à ce que vous trouviez l'entier cible ou que vous atteigniez la fin de la liste. Si vous atteignez la fin de la liste, l'entier cible est le plus petit entier qui ne figure pas dans la liste. Si vous atteignez la fin des entiers 64 bits, chaque entier 64 bits est dans la liste.
La voici en tant que fonction Python:
def smallest_missing_uint64(source_list):
the_answer = None
target = 0L
while target < 2L**64:
target_found = False
for item in source_list:
if item == target:
target_found = True
if not target_found and the_answer is None:
the_answer = target
target += 1L
return the_answer
Cette fonction est délibérément inefficace pour conserver O (n). Notez en particulier que la fonction continue à vérifier les entiers cibles même après que la réponse a été trouvée. Si la fonction est renvoyée dès que la réponse a été trouvée, le nombre de fois que la boucle externe a été exécutée serait lié à la taille de la réponse, qui est liée à n. Ce changement rendrait le temps d'exécution O (n ^ 2), même s'il serait beaucoup plus rapide.
Unordered_set peut être utilisé pour stocker tous les nombres positifs, puis on peut parcourir de 1 à la longueur de unordered_set et voir le premier nombre qui ne se produit pas.
int firstMissingPositive(vector<int>& nums) {
unordered_set<int> fre;
// storing each positive number in a hash.
for(int i = 0; i < nums.size(); i +=1)
{
if(nums[i] > 0)
fre.insert(nums[i]);
}
int i = 1;
// Iterating from 1 to size of the set and checking
// for the occurrence of 'i'
for(auto it = fre.begin(); it != fre.end(); ++it)
{
if(fre.find(i) == fre.end())
return i;
i +=1;
}
return i;
}
Voici une réponse en Java qui ne modifie pas l'entrée et utilise le temps O(N), N bits plus une surcharge de mémoire constante (où N est la taille de la liste):
int smallestMissingValue(List<Integer> values) {
BitSet bitset = new BitSet(values.size() + 1);
for (int i : values) {
if (i >= 0 && i <= values.size()) {
bitset.set(i);
}
}
return bitset.nextClearBit(0);
}
def solution(A):
index = 0
target = []
A = [x for x in A if x >=0]
if len(A) ==0:
return 1
maxi = max(A)
if maxi <= len(A):
maxi = len(A)
target = ['X' for x in range(maxi+1)]
for number in A:
target[number]= number
count = 1
while count < maxi+1:
if target[count] == 'X':
return count
count +=1
return target[count-1] + 1
Vous avez 100% pour la solution ci-dessus.
cela peut aider:
0- A is [5, 3, 2, 7];
1- Define B With Length = A.Length; (O(1))
2- initialize B Cells With 1; (O(n))
3- For Each Item In A:
if (B.Length <= item) then B[Item] = -1 (O(n))
4- The answer is smallest index in B such that B[index] != -1 (O(n))
Solution via javascript basique
var a = [1, 3, 6, 4, 1, 2];
function findSmallest(a) {
var m = 0;
for(i=1;i<=a.length;i++) {
j=0;m=1;
while(j < a.length) {
if(i === a[j]) {
m++;
}
j++;
}
if(m === 1) {
return i;
}
}
}
console.log(findSmallest(a))
J'espère que cela aide pour quelqu'un.
1) Filtre négatif et zéro
2) Trier/distinct
3) tableau de visite
Complexité : O(N) ou O (N * log (N))
en utilisant Java8
public int solution(int[] A) {
int result = 1;
boolean found = false;
A = Arrays.stream(A).filter(x -> x > 0).sorted().distinct().toArray();
//System.out.println(Arrays.toString(A));
for (int i = 0; i < A.length; i++) {
result = i + 1;
if (result != A[i]) {
found = true;
break;
}
}
if (!found && result == A.length) {
//result is larger than max element in array
result++;
}
return result;
}
La réponse Dafny fragment de Ants 'montre pourquoi l'algorithme en place peut échouer. La pré-condition requires
indique que les valeurs de chaque élément ne doivent pas dépasser les limites du tableau.
method AntsAasma(A: array<int>) returns (M: int)
requires A != null && forall N :: 0 <= N < A.Length ==> 0 <= A[N] < A.Length;
modifies A;
{
// Pass 1, move every value to the position of its value
var N := A.Length;
var cursor := 0;
while (cursor < N)
{
var target := A[cursor];
while (0 <= target < N && target != A[target])
{
var new_target := A[target];
A[target] := target;
target := new_target;
}
cursor := cursor + 1;
}
// Pass 2, find first location where the index doesn't match the value
cursor := 0;
while (cursor < N)
{
if (A[cursor] != cursor)
{
return cursor;
}
cursor := cursor + 1;
}
return N;
}
Collez le code dans le validateur avec et sans la clause forall ...
pour voir l'erreur de vérification. La deuxième erreur est due au fait que le vérificateur n'a pas pu établir une condition de terminaison pour la boucle Passe 1. Prouver cela est laissé à quelqu'un qui comprend mieux l'outil.
Avec python ce n’est pas le plus efficace, mais correct
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import datetime
# write your code in Python 3.6
def solution(A):
MIN = 0
MAX = 1000000
possible_results = range(MIN, MAX)
for i in possible_results:
next_value = (i + 1)
if next_value not in A:
return next_value
return 1
test_case_0 = [2, 2, 2]
test_case_1 = [1, 3, 44, 55, 6, 0, 3, 8]
test_case_2 = [-1, -22]
test_case_3 = [x for x in range(-10000, 10000)]
test_case_4 = [x for x in range(0, 100)] + [x for x in range(102, 200)]
test_case_5 = [4, 5, 6]
print("---")
a = datetime.datetime.now()
print(solution(test_case_0))
print(solution(test_case_1))
print(solution(test_case_2))
print(solution(test_case_3))
print(solution(test_case_4))
print(solution(test_case_5))
J'aime l'approche "suppose zéro". Si les nombres étaient aléatoires, zéro est hautement probable. Si "l'examinateur" définit une liste non aléatoire, ajoutez-en une et devinez-la à nouveau:
LowNum=0
i=0
do forever {
if i == N then leave /* Processed entire array */
if array[i] == LowNum {
LowNum++
i=0
}
else {
i++
}
}
display LowNum
Le pire des cas est n * N avec n = N, mais dans la pratique, il est fort probable que n soit un petit nombre (par exemple, 1)
def solution(A):
A.sort()
j = 1
for i, elem in enumerate(A):
if j < elem:
break
Elif j == elem:
j += 1
continue
else:
continue
return j
Comme Stephen C l'a intelligemment souligné, la réponse doit être un nombre inférieur à la longueur du tableau. Je trouverais alors la réponse par recherche binaire. Cela optimise le pire des cas (de sorte que l'intervieweur ne peut pas vous prendre dans un scénario pathologique «et si»). Dans une interview, précisez que vous faites cela pour optimiser le pire des cas.
Pour utiliser la recherche binaire, vous devez soustraire le nombre que vous recherchez de chaque élément du tableau et rechercher les résultats négatifs.
Bravo aux fourmis Aasma! J'ai réfléchi à la réponse pendant environ 15 minutes et, indépendamment, j'ai trouvé une réponse semblable à la vôtre:
#define SWAP(x,y) { numerictype_t tmp = x; x = y; y = tmp; }
int minNonNegativeNotInArr (numerictype_t * a, size_t n) {
int m = n;
for (int i = 0; i < m;) {
if (a[i] >= m || a[i] < i || a[i] == a[a[i]]) {
m--;
SWAP (a[i], a[m]);
continue;
}
if (a[i] > i) {
SWAP (a[i], a[a[i]]);
continue;
}
i++;
}
return m;
}
m représente "la sortie maximale possible actuelle en fonction de ce que je sais des i premières entrées et en supposant que rien d'autre ne concerne les valeurs jusqu'à l'entrée à m-1".
Cette valeur de m ne sera renvoyée que si (a [i], ..., a [m-1]) est une permutation des valeurs (i, ..., m-1). Ainsi, si a [i]> = m ou si a [i] <i ou si a [i] == a [a [i]], nous savons que m est une sortie incorrecte et doit être au moins un élément inférieur. Donc décrémenter m et échanger un [i] avec un [[m] nous pouvons recurse.
Si ce n'est pas vrai mais un [i]> i alors sachant qu'un [i]! = Un [un [i]] nous savons que l'échange d'un [i] avec un [un [i]] augmentera le nombre d'éléments à leur place.
Sinon, un [i] doit être égal à i, auquel cas nous pouvons incrémenter i sachant que toutes les valeurs inférieures ou égales à cet indice sont égales à leur index.
La preuve que cela ne peut pas entrer dans une boucle infinie est laissée comme exercice au lecteur. :)
Je ne suis pas sûr d'avoir la question. Mais si pour la liste 1, 2, 3, 5, 6 et le nombre manquant est 4, alors le nombre manquant peut être trouvé dans O(n) par: (N + 2) (n + 1 )/2- (n + 1) n/2
EDIT: désolé, je pense que je pensais trop vite la nuit dernière. Quoi qu'il en soit, la deuxième partie devrait en fait être remplacée par sum (liste), qui est l'endroit où O(n) vient. La formule révèle l’idée qui la sous-tend: pour n entiers séquentiels, la somme doit être (n + 1) * n/2. S'il existe un nombre manquant, la somme serait égale à la somme de (n + 1) nombres entiers séquentiels moins le nombre manquant.
Merci d'avoir souligné le fait que je mettais quelques pièces du milieu dans mon esprit.