web-dev-qa-db-fra.com

Trouver des doublons dans un tableau avec une approche efficace en mémoire

A est un tableau d'entiers.

Toutes les valeurs sont comprises entre 0 à A.Length-1

ça veut dire 0 <= A[i] <= A.Length-1

Je suis censé trouver des éléments répétitifs; et s'il y a plusieurs éléments répétitifs, choisissez celui qui a un indice inférieur pour l'élément répété.

par exemple:

a = [3, 4, 2, 5, 2, 3]

ensuite

result = 2

C'était une question d'entrevue. J'ai utilisé un autre tableau pour stocker des éléments et vérifier quand il se répète. Ensuite, cela m'a donné du temps pour certains cas de test. L'intervieweur a conseillé de ne boucler sur le tableau qu'une seule fois et de ne créer aucune structure de données supplémentaire.

22
Kiana Montazeri

Pas besoin d'une autre structure de données. Vous pouvez utiliser l'entrée elle-même comme un hachage.

Chaque fois que vous voyez une valeur, ajoutez A.Length à l'élément qui correspond à cet index. Comme les valeurs ont peut-être déjà été incrémentées, vous devez considérer la valeur comme A[i] mod A.length.

Si vous trouvez un élément qui est déjà> = A.longueur .. vous avez une répétition. (N'oubliez pas que le problème indique que tous les éléments se trouvent dans l'intervalle [0, A.Length-1])

Suivez l'indice le plus bas qui a été trouvé comme répété.

Il en résulte O(N) complexité (passage unique) et aucune utilisation d'une structure de données supplémentaire, c'est-à-dire taille O (1)

Le concept clé derrière cette approche est que les hashsets fonctionnent de cette façon. Conceptuellement, cela est indirectement lié au principe du pigeonnier. https://en.wikipedia.org/wiki/Pigeonhole_principle

Remarque: Au cours de l'entretien, il serait important de poser des questions spécifiques à la mise en œuvre, de discuter des limites, des hypothèses, etc.: - Quel est le type de données des éléments de la liste? - si les valeurs sont dans la plage [0..A.length-1], tous les éléments ne sont-ils pas signés ou puis-je utiliser des nombres négatifs si je le souhaite? - etc.

Pendant l'entrevue, je ne dirais pas que c'est une réponse parfaite, je discuterais plutôt des hypothèses avec l'intervieweur et je les ajusterais en conséquence. Par exemple, une autre réponse a suggéré d'utiliser des nombres négatifs, mais il est possible que le type de données des éléments soit un type non signé, etc.

L'entretien est censé déclencher une discussion technique pour explorer à la fois vos connaissances et votre créativité.

22
Juan Leni

Remarque: la solution échoue s'il y a un élément avec une valeur de zéro. La solution d'Olivier peut gérer de tels cas.

Rendre l'élément avec un indice de A [i] négatif. Il ne passe qu'une seule fois dans la boucle.

for(int i=0; i<A.Length; i++)
    {
        if (A[Math.Abs(A[i])] < 0){ return Math.Abs(A[i]);}
        A[Math.Abs(A[i])] = -A[Math.Abs(A[i])];
    }
6
Aryan Firouzian

Je voudrais affiner la solution de @ AryanFirouzian et retourner tous les doublons en utilisant yield return. De plus, l'utilisation d'une variable temporaire simplifie le code.

public static IEnumerable<int> FindDuplicates(int[] A)
{
    for (int i = 0; i < A.Length; i++) {
        int absAi = Math.Abs(A[i]);
        if (A[absAi] < 0) {
            yield return absAi;
        } else {
            A[absAi] *= -1;
        }
    }
}

Cependant, cette solution ne retourne pas l'élément avec l'index inférieur et s'il y a plus de 2 copies identiques, il renverra la même valeur plus d'une fois. Un autre problème est que 0 ne peut pas être rendu négatif.

Une meilleure solution élimine les résultats répétés, mais renvoie toujours le deuxième index et a un problème avec 0 valeurs. Il renvoie également l'index lui-même pour illustrer le problème d'index incorrect

public static IEnumerable<(int index, int value)> FindDuplicates(int[] A)
{
    for (int i = 0; i < A.Length; i++) {
        int x = A[i] % A.Length;
        if (A[x] / A.Length == 1) {
            yield return (i, x);
        }
        A[x] += A.Length;
    }
}

Testé avec

var A = new int[] { 3, 4, 2, 5, 2, 3, 3 };
foreach (var item in FindDuplicates(A)) {
    Console.WriteLine($"[{item.index}] = {item.value}");
}

Il revient

[4] = 2
[5] = 3

Ma solution finale qui élimine tous ces problèmes (du moins je l'espère): elle code le premier index lui-même en ajoutant (i + 1) * A.Length À la première occurrence d'une valeur. (i + 1) Car i peut être 0. L'index peut ensuite être décodé avec l'opération inverse (A[x] / A.Length) - 1.

Ensuite, parce que nous voulons renvoyer un résultat uniquement sur la première valeur répétitive, nous définissons la valeur sur une valeur négative pour l'exclure du traitement ultérieur. Par la suite, la valeur d'origine peut être récupérée avec Math.Abs(A[i]) % A.Length.

public static IEnumerable<(int index, int value)> FindDuplicates(int[] A)
{
    for (int i = 0; i < A.Length; i++) {
        int x = Math.Abs(A[i]) % A.Length;
        if (A[x] >= 0) {
            if (A[x] < A.Length) { // First occurrence.
                A[x] += (i + 1) * A.Length; // Encode the first index.
            } else { // Second occurrence.
                int firstIndex = (A[x] / A.Length) - 1; // Decode the first index.
                yield return (firstIndex, x);

                // Mark the value as handeled by making it negative;
                A[x] *= -1; // A[x] is always >= A.Length, so no zero problem.
            }
        }
    }
}

Renvoie le résultat attendu

[2] = 2
[0] = 3

Nos éléments sont des ints qui n'ont pas d'identité. C'est à dire. nous pouvons renvoyer l'un des doublons à n'importe quel index car deux entiers égaux ne peuvent pas être distingués. Dans le cas où les éléments ont une identité (ils peuvent être des types de référence avec des valeurs égales mais des références différentes ou avoir des champs supplémentaires non impliqués dans les tests d'égalité), nous devons renvoyer la première occurrence avec

yield return (firstIndex, Math.Abs(A[firstIndex]) % A.Length);

pour satisfaire toutes les exigences.

3

Pour qui veut une implémentation du problème, je propose deux variantes (en c # comme dans les balises), une utilisant la réponse acceptée et une utilisant l'approche d'une autre réponse, utilisant l'opposé des éléments. Cependant, la dernière solution a un problème avec la valeur zéro et nécessite une astuce.

Première solution

using System;
public class Program
{
    public static void Main()
    {
        int[] a = {3, 4, 0, 5, 2, 3};
        int N = 6;
        int min_index = 0; 
        bool found = false;
        int index = -1;
        int i = 0;
        while(i < N && !found)
        {

            if(a[i] >= N) 
                index = a[i] % N;
            else
                index = a[i];

            if(a[index] >= N) //its a duplicated elements 
            {
                min_index = i;
                found = true;
            }else
            {
                a[index] += N;
            }
            i++;

        }

        Console.WriteLine("Result = " + a[min_index] % N);
    }
}

Deuxième solution

    using System;
public class Program
{
    public static void Main()
    {
        int[] a = {3, 4, 2, 5, 2, 3};
        int N = 6;
        int min_index = N-1; 
        bool found = false;
        int index = -1;
        int i = 0;
        while(i < N && !found)
        {
            if(a[i] == -N+1) //it was 0
                index = 0;
            else
                index = Math.Abs(a[i]);

            if(a[index] < 0 || a[index] == -N+1) //its a duplicated elements 
            {
                min_index = i;
                found = true;
            }else
            {
                if(a[index] > 0)
                {
                    a[index] = -a[index];
                }else
                {
                    a[index] += -N+1;
                }
            }
            i++;
        }

        if(a[min_index] == -N+1)
            a[min_index] = 0;

        Console.WriteLine("Result = " + Math.Abs(a[min_index]));
    }
}
2
Andrea Bellizzi