web-dev-qa-db-fra.com

Trouvez facilement un ensemble de pièces de taille 40, 400 ou 4000

Relatif au problème classique trouver un entier non compris entre 4 milliards mais pas exactement le même.

Pour clarifier, par integers, ce que je veux vraiment dire n’est qu’un sous-ensemble de sa définition mathématique. En d’autres termes, supposons qu’il n’ya qu’un nombre fini d’entiers. Disons en C++, ils sont int dans la plage de [INT_MIN, INT_MAX].

Maintenant, étant donné un std::vector<int> (pas de doublons) ou std::unordered_set<int>, dont la taille peut être 40, 400, 4000 ou plus, mais pas trop, comment générer efficacement un nombre qui est garanti ne pas être parmi ceux donnés

S'il n'y a pas d'inquiétude pour le débordement, je pourrais alors multiplier toutes les valeurs non nulles et ajouter le produit de 1. Mais il y en a. Les tests élémentaires de l'adversaire pourraient contenir délibérément INT_MAX.

Je suis plus en faveur des approches simples et non aléatoires. Y a-t-il?

Je vous remercie!

Mise à jour: pour lever les ambiguïtés, supposons un std::vector<int> non trié qui ne contienne aucun doublon. Je demande donc s'il y a quelque chose de mieux que O (n log (n)). Veuillez également noter que les scénarios de test peuvent contenir à la fois INT_MIN et INT_MAX.

29
fleix

Vous pouvez simplement renvoyer le premier de N+1 nombres entiers candidats non contenus dans votre entrée. Les candidats les plus simples sont les nombres 0 à N. Cela nécessite O(N) space and time.

 int find_not_contained(container<int> const&data)
 {
     const int N=data.size();
     std::vector<char> known(N+1, 0);   // one more candidates than data
     for(int i=0; i< N; ++i)
         if(data[i]>=0 && data[i]<=N)
             known[data[i]]=1;
     for(int i=0; i<=N; ++i)
         if(!known[i])
             return i;
     assert(false);                     // should never be reached.
 }

Les méthodes aléatoires peuvent utiliser moins d'espace, mais peuvent nécessiter davantage de passages sur les données dans le pire des cas.

31
Walter

Les méthodes aléatoires sont en effet très efficaces ici.

Si nous voulons utiliser une méthode déterministe et en supposant que la taille n n’est pas trop grande, 4000 par exemple, nous pouvons créer un vecteur x de taille m = n + 1 (ou un peu plus grand, 4096 pour exemple pour faciliter le calcul), initialisé avec 0.

Pour chaque i dans la plage, nous venons de définir x [array [i] modulo m] = 1. 

Ensuite, une simple O(n) recherche dans x fournira une valeur qui ne se trouve pas dans array

Remarque: l'opération modulo n'est pas exactement l'opération "%"

Edit: J'ai mentionné que les calculs sont facilités en sélectionnant ici une taille de 4096. Pour être plus concret, cela signifie que l'opération modulo est effectuée avec une simple opération &.

9
Damien

Vous pouvez trouver le plus petit entier inutilisé dans le temps O(N) en utilisant l'espace auxiliaire O(1) si vous êtes autorisé à réorganiser le vecteur d'entrée à l'aide de l'algorithme suivant. [Note 1] (L'algorithme fonctionne également si le vecteur contient des données répétées.)

size_t smallest_unused(std::vector<unsigned>& data) {
  size_t N = data.size(), scan = 0;
  while (scan < N) {
    auto other = data[scan];
    if (other < scan && data[other] != other) {
      data[scan] = data[other];
      data[other] = other;
    }
    else
      ++scan;
  }
  for (scan = 0; scan < N && data[scan] == scan; ++scan) { }
  return scan;
}

La première passe garantit que, si une k dans la plage [0, N) a été trouvée après la position k, elle est maintenant présente à la position k. Cette réorganisation est effectuée par permutation afin d'éviter de perdre des données. Une fois l'analyse terminée, la première entrée dont la valeur n'est pas identique à son index n'est référencée nulle part dans le tableau.

Cette assertion peut ne pas être évidente à 100%, car une entrée pourrait être référencée à partir d'un index antérieur. Cependant, dans ce cas, l'entrée ne pourrait pas être la première entrée sans égaler son index, car l'entrée la plus récente satisferait à ce critère.

Pour voir que cet algorithme est O (N), il convient de noter que l’échange aux lignes 6 et 7 ne peut se produire que si l’entrée cible n’est pas égale à son index et qu'après l’échange, l’entrée cible est égale à son index . Donc, au maximum N échanges peuvent être effectués, et la condition if à la ligne 5 sera true au maximum N fois. D'autre part, si la condition if est false, scan sera incrémenté, ce qui ne peut se produire que N fois. Donc, l'instruction if est évaluée au maximum 2N fois (qui est O (N)).


Remarques:

  1. J'ai utilisé des entiers non signés ici parce que cela rend le code plus clair. L'algorithme peut facilement être ajusté pour les entiers signés, par exemple en mappant des entiers signés de [INT_MIN, 0) sur des entiers non signés [INT_MAX, INT_MAX - INT_MIN) (La soustraction est mathématique, et non selon la sémantique C qui n'autorise pas la représentation du résultat.) En complément de 2, c'est le même motif de bits. Cela change évidemment l'ordre des nombres, ce qui affecte la sémantique du "plus petit entier non utilisé"; un mappage préservant les commandes pourrait également être utilisé.
6
rici

Faites un x aléatoire (INT_MIN..INT_MAX) et testez-le contre tous. Testez x ++ en cas d'échec (cas très rare pour 40/400/4000). 

4
Alexey Birukov

Étape 1: Triez le vecteur.

Cela peut être fait dans O (n log (n)), vous pouvez trouver plusieurs algorithmes en ligne, utilisez celui que vous préférez.

Étape 2: Trouver le premier int pas dans le vecteur.

Itérez facilement d'INT_MIN à INT_MIN + 40/400/4000 en vérifiant si le vecteur a l'int en cours:

Pseudocode:

SIZE = 40|400|4000 // The one you are using
for (int i = 0; i < SIZE; i++) {
    if (array[i] != INT_MIN + i)
        return INT_MIN + i;

La solution serait O (n log (n) + n) qui signifie: O (n log (n))


Édition: viens de lire votre édition demandant quelque chose de meilleur que O (n log (n)), désolé.

2
dquijada

Dans le cas où les entiers sont fournis dans un std::unordered_set<int> (par opposition à un std::vector<int>), vous pouvez simplement parcourir la plage de valeurs entières jusqu'à ce que vous rencontriez une valeur entière qui n'est pas présente dans le unordered_set<int>. La recherche de la présence d'un entier dans un std::unordered_set<int> est assez simple, car std::unodered_set permet d'effectuer une recherche via sa fonction membre find()

La complexité spatiale de cette approche serait O(1).


Si vous commencez à parcourir la valeur minimum possible pour une int (c'est-à-dire, std::numeric_limits<int>::min()), vous obtiendrez la minimumint ne figurant pas dans le std::unordered_set<int>:

int find_lowest_not_contained(const std::unordered_set<int>& set) {
   for (auto i = std::numeric_limits<int>::min(); ; ++i) {
      auto it = set.find(i); // search in set
      if (it == set.end()) // integer not in set?
         return *it;
   }
}

De manière analogue, si vous commencez à parcourir la valeur greatest possible pour une int (c'est-à-dire std::numeric_limits<int>::max()), vous obtiendrez la minimumint ne figurant pas dans le std::unordered_set<int>:

int find_greatest_not_contained(const std::unordered_set<int>& set) {
   for (auto i = std::numeric_limits<int>::max(); ; --i) {
      auto it = set.find(i); // search in set
      if (it == set.end()) // integer not in set?
         return *it;
   }
}

En supposant que les ints soient uniformément mappés par la fonction de hachage dans les compartiments de unordered_set<int>, une opération de recherche sur unordered_set<int> peut être réalisée en temps constant. La complexité de l'exécution serait alors O (M), où M est la taille de la plage entière que vous recherchez pour une valeur non contenue. M est limité par la taille du unordered_set<int> (c'est-à-dire, dans votre cas, M <= 4000).

En effet, avec cette approche, la sélection de toute plage entière dont la taille est supérieure à la taille du unordered_set se heurte à une valeur entière qui n’est pas présente dans le unordered_set<int>.

0
El Profesor