La réponse brillante de caf imprime chaque nombre qui apparaît k fois dans le tableau k-1 fois. C'est un comportement utile, mais la question demande sans doute que chaque duplicata soit imprimé une seule fois, et il fait allusion à la possibilité de le faire sans faire sauter les limites linéaires temps / espace constant. Cela peut être fait en remplaçant sa deuxième boucle par le pseudocode suivant:
for (i = 0; i < N; ++i) {
if (A[i] != i && A[A[i]] == A[i]) {
print A[i];
A[A[i]] = i;
}
}
Cela exploite la propriété qu'après l'exécution de la première boucle, si une valeur m
apparaît plus d'une fois, alors l'une de ces apparences est garantie d'être dans la bonne position, à savoir A[m]
. Si nous faisons attention, nous pouvons utiliser cet emplacement «d'origine» pour stocker des informations indiquant si des doublons ont déjà été imprimés ou non.
Dans la version de caf, lorsque nous avons parcouru le tableau, A[i] != i
cela impliquait qu'il s'agissait d' A[i]
un doublon. Dans ma version, je m'appuie sur un invariant légèrement différent: cela A[i] != i && A[A[i]] == A[i]
implique qu'il A[i]
s'agit d'un doublon que nous n'avons jamais vu auparavant . (Si vous supprimez la partie "que nous n'avons jamais vu auparavant", le reste peut être vu comme étant impliqué par la vérité de l'invariant de caf et la garantie que tous les doublons ont une copie dans un emplacement d'origine.) Cette propriété tient à le début (après la fin de la première boucle de caf) et je montre ci-dessous qu'elle est maintenue après chaque étape.
Au fur et à mesure que nous parcourons le tableau, le succès A[i] != i
du test implique qu'il A[i]
pourrait s'agir d' un doublon qui n'a jamais été vu auparavant. Si nous ne l 'avons pas vu auparavant, nous nous attendons à ce que l A[i]
' emplacement d 'origine de la personne se dirige vers lui - c'est ce qui est testé par la seconde moitié de la if
condition. Si tel est le cas, nous l'imprimons et modifions l'emplacement du domicile pour qu'il pointe vers ce premier doublon trouvé, créant un «cycle» en 2 étapes.
Pour voir que cette opération ne modifie pas notre invariant, supposons m = A[i]
qu'une position particulière soit i
satisfaisante A[i] != i && A[A[i]] == A[i]
. Il est évident que le changement que nous faisons ( A[A[i]] = i
) travaillera pour éviter que d' autres événements non domestiques de m
d'être sortie comme des doublons en provoquant la 2ème moitié de leurs if
conditions à l' échec, mais il lorsqu'il i
arrive à l'endroit de la maison, m
? Oui, car maintenant, même si à ce nouveau i
nous constatons que la première moitié de la if
condition A[i] != i
est vraie, la deuxième moitié teste si l'emplacement vers lequel elle pointe est un emplacement d'origine et constate que ce n'est pas le cas. Dans cette situation, nous ne savons plus si m
ou A[m]
était la valeur en double, mais nous savons que de toute façon,cela a déjà été signalé , car ces 2 cycles sont garantis de ne pas apparaître dans le résultat de la 1ère boucle de caf. (Notez que si m != A[m]
alors exactement l'un des m
et A[m]
se produit plus d'une fois, et l'autre ne se produit pas du tout.)
a[a[i]]
, et la contrainte d'espace O (1) indique que l'swap()
opération est la clé.