Moyen efficace de rechercher un élément

88

Récemment, j'ai eu une interview, où ils m'ont posé une question de « recherche ».
La question était:

Supposons qu'il existe un tableau d'entiers (positifs), dont chaque élément est l'un +1ou l' autre ou -1comparé à ses éléments adjacents.

Exemple:

array = [4,5,6,5,4,3,2,3,4,5,6,7,8];

Recherchez maintenant 7et restaurez sa position.

J'ai donné cette réponse:

Stockez les valeurs dans un tableau temporaire, triez-les, puis appliquez la recherche binaire.

Si l'élément est trouvé, retournez sa position dans le tableau temporaire.
(Si le nombre se produit deux fois, renvoie sa première occurrence)

Mais, ils ne semblaient pas satisfaits de cette réponse.

Quelle est la bonne réponse?

NSUser
la source
4
Autant que je sache, une recherche linéaire est un bon moyen de localiser l'index d'un élément dans le tableau. Je ne suis pas encore sûr d'un autre algorithme de recherche efficace pour localiser l'index d'un élément.
Sean Francis N.Ballais
4
Si 7 est garanti pour n'apparaître qu'une seule fois ou si le 7 renvoyé n'a pas d'importance, vous pouvez améliorer encore l'algorithme linéaire de la réponse de coleman.
user1942027
52
Si votre solution originale nécessite un tri, c'est pire que la recherche linéaire naïve. Vous semblez ne pas en être conscient.
cubuspl42
5
Le tri nécessite O (nlogn) et une recherche binaire est O (logn). Si vous avez besoin de rechercher de nombreuses valeurs dans le grand tableau, votre réponse peut être meilleure, mais si vous ne recherchez qu'une seule fois, les algorithmes O (n) peuvent être meilleurs.
jingyu9575
23
Je ne sais pas pourquoi personne d'autre n'a mentionné cela: votre méthode était non seulement inefficace, elle était incorrecte , et c'est bien pire qu'une simple inefficacité. L'exigence concerne la position d'un nombre donné dans le tableau d'origine . Votre méthode renvoie la position du nombre dans un tableau trié . Maintenant, vous pouvez récupérer la position d'origine, en convertissant le tableau simple en un tableau de tuples (nombre, orig_pos) avant le tri. Mais vous ne l'avez pas mentionné, donc je suppose que vous ne l'avez pas mentionné non plus dans l'interview.
Tom Zych

Réponses:

126

Vous pouvez faire une recherche linéaire avec des pas qui sont souvent supérieurs à 1. L'observation cruciale est que si par exemple array[i] == 4et 7 n'est pas encore apparu, le prochain candidat pour 7 est à l'index i+3. Utilisez une boucle while qui va à plusieurs reprises directement au prochain candidat viable.

Voici une implémentation, légèrement généralisée. Il trouve la première occurrence de kdans le tableau (sous réserve de la restriction + = 1) ou -1si elle ne se produit pas:

#include <stdio.h>
#include <stdlib.h>

int first_occurence(int k, int array[], int n);

int main(void){
    int a[] = {4,3,2,3,2,3,4,5,4,5,6,7,8,7,8};
    printf("7 first occurs at index %d\n",first_occurence(7,a,15));
    printf("but 9 first \"occurs\" at index %d\n",first_occurence(9,a,15));
    return 0;
}

int first_occurence(int k, int array[], int n){
    int i = 0;
    while(i < n){
        if(array[i] == k) return i;
        i += abs(k-array[i]);
    }
    return -1;
}

production:

7 first occurs at index 11
but 9 first "occurs" at index -1
John Coleman
la source
8
Précisément ce que je pensais. Oui O(N), mais je ne pense pas qu'il existe un moyen plus rapide de le faire.
shapiro yaacov
2
Vous pourriez le faire un peu plus rapidement en moyenne avec plus de candidats (par exemple, premier et dernier), puis choisir celui qui est le plus proche de la cible - c'est-à-dire si vous n'avez besoin de trouver qu'une seule occurrence, pas la première.
mkadunc
2
@mkadunc C'est une bonne idée. Une autre observation est que si le premier et le dernier éléments chevauchent 7, dans ce cas particulier, vous pouvez utiliser une recherche binaire (si vous ne vous souciez pas du 7 que vous trouvez)
John Coleman
1
Dans le cas où vous avez besoin de trouver 7 (pas nécessairement le premier), je propose l'amélioration (pratique) suivante. Faites une liste de sections (deux entiers, «début» et «fin») et au lieu de commencer au début du tableau, commencez au milieu. Selon la valeur de la cellule, ignorez la plage appropriée et ajoutez les deux sections restantes à votre liste de sections. Répétez maintenant pour l'élément suivant de la liste. C'est toujours 'O (n)', mais vous ignorez deux fois la plage à chaque fois que vous vérifiez une cellule.
shapiro yaacov
3
@ShapiroYaacov: Combiné avec la vérification si l'intervalle de la valeur la plus basse à la plus élevée des valeurs des deux côtés d'une section inclut k (7), cela mérite une réponse en soi.
greybeard
35

Votre approche est trop compliquée. Vous n'avez pas besoin d'examiner chaque élément du tableau. La première valeur est 4, il en 7va de même pour au moins les 7-4 éléments, et vous pouvez les ignorer.

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    int array[] = {4,5,6,5,4,3,2,3,4,5,6,7,8};
    int len = sizeof array / sizeof array[0];
    int i = 0;
    int steps = 0;
    while (i < len && array[i] != 7) {
        i += abs(7 - array[i]);
        steps++;
    }

    printf("Steps %d, index %d\n", steps, i);
    return 0;
}

Sortie du programme:

Steps 4, index 11

Edit: amélioré après les commentaires de @Raphael Miedl et @Martin Zabel.

Girouette
la source
2
Un pinaillage, if ((skip = 7 - array[i]) < 1) skip = 1;semble trop le compliquer et le pessimiser à mon avis. Si array[i] == 200vous obtenez -193et sautez de 1 à chaque fois, même si vous pouvez sauter tous les 193. Pourquoi pas simplement i += abs(7 - array[i])?
user1942027
1
Vous devez définir skipla différence absolue entre 7 et array[i].
Martin Zabel
@Raphael Miedl non, un élément ne sera pas 200, vous l'auriez passé 7.
Weather Vane
3
@WeatherVane nous n'avons pas cette garantie, seulement que les valeurs adjacentes sont +1/ les -1unes des autres. Donc ça pourrait juste être array[0] == 200et les autres sont pour -1la plupart .
user1942027
1
@WeatherVane cela suppose que l'élément est toujours trouvé dans le tableau, ce qui peut ne pas être le cas. -1 est un retour valide dans ce cas; ce qui change un peu le code que vous avez
Eugene
20

Une variante de la recherche linéaire conventionnelle pourrait être une bonne solution. Prenons un élément disons array[i] = 2. Maintenant, array[i + 1]sera 1 ou 3 (impair), array[i + 2]sera (entiers positifs uniquement) 2 ou 4 (nombre pair).

En continuant ainsi, un modèle est observable - array[i + 2*n]contiendra des nombres pairs et ainsi tous ces indices peuvent être ignorés.

Aussi, nous pouvons voir que

array[i + 3] = 1 or 3 or 5
array[i + 5] = 1 or 3 or 5 or 7

Ainsi, l'index i + 5doit être vérifié ensuite et une boucle while peut être utilisée pour déterminer le prochain index à vérifier, en fonction de la valeur trouvée à l'index i + 5.

Bien que cela soit complexe O(n)(temps linéaire en termes de complexité asymptotique), il vaut mieux qu'une recherche linéaire normale en termes pratiques car tous les indices ne sont pas visités.

Évidemment, tout cela sera inversé si array[i](notre point de départ) était étrange.

Madhav Datt
la source
8

L'approche présentée par John Coleman est ce que l'intervieweur espérait, selon toute probabilité.
Si vous êtes prêt à aller un peu plus compliqué, vous pouvez augmenter la longueur de saut attendue:
Appelez la valeur cible k . Commencez par la valeur du premier élément v à la position p et appelez la différence kv dv avec la valeur absolue av . Pour accélérer les recherches négatives, jetez un œil au dernier élément comme l'autre valeur u à la position o: si dv × du est négatif, k est présent (si une occurrence de k est acceptable, vous pouvez restreindre la plage d'index ici comme le fait la recherche binaire). Si av + au est supérieur à la longueur du tableau, k est absent. (Si dv × du zéro est, v ou u est égal à k.)
Omettre validité d'index: sonde le ( « suivant ») position où la séquence pourrait revenir à v avec k au milieu: o = p + 2*av.
Si dv × du est négatif, trouver k (récursivement?) De p + av à o-au;
s'il est nul, u vaut k en o.
Si du est égal à dv et que la valeur au milieu n'est pas k, ou au dépasse av,
ou si vous ne trouvez pas k de p + av à o-au,
laissez p=o; dv=du; av=au;et continuez à sonder.
(Pour un flash-back complet sur les textes des années 60, regardez avec Courier. Ma "1ère 2ème pensée" était d'utilisero = p + 2*av - 1, ce qui exclut du égal à dv .)

barbe grise
la source
3

ÉTAPE 1

Commencez par le premier élément et vérifiez s'il est 7. Disons que cc'est l'indice de la position actuelle. Donc, au départ, c = 0.

ÉTAPE 2

Si c'est 7, vous avez trouvé l'index. C'est c. Si vous avez atteint la fin du tableau, sortez.

ÉTAPE 3

Si ce n'est pas le cas, alors 7 doit être au moins à des |array[c]-7|positions éloignées car vous ne pouvez ajouter qu'une unité par index. Par conséquent, ajoutez |array[c]-7|à votre index actuel, c, et passez à nouveau à l'étape 2 pour vérifier.

Dans le pire des cas, lorsqu'il y a des alternances de 1 et -1, la complexité temporelle peut atteindre O (n), mais les cas moyens seraient livrés rapidement.

Akeshwar Jha
la source
En quoi est-ce différent de la réponse de John Coleman? (En plus de suggérer |c-7|où cela |array[c]-7|semble nécessaire.)
greybeard
Je viens de voir sa réponse. J'avoue que l'idée de base est la même.
Akeshwar Jha
La question d'origine ne stipule pas que le tableau commence par un nombre inférieur à 7. Il array[c]-7peut donc être positif ou négatif. Vous devez en faire la demande abs()avant le saut.
arielf
Oui tu as raison. Voilà pourquoi j'utilise array[c] - 7avec l'opérateur de module, |array[c] - 7|.
Akeshwar Jha
3

Ici, je donne l'implémentation en java ...

public static void main(String[] args) 
{       
    int arr[]={4,5,6,5,4,3,2,3,4,5,6,7,8};
    int pos=searchArray(arr,7);

    if(pos==-1)
        System.out.println("not found");
    else
        System.out.println("position="+pos);            
}

public static int searchArray(int[] array,int value)
{
    int i=0;
    int strtValue=0;
    int pos=-1;

    while(i<array.length)
    {
        strtValue=array[i];

        if(strtValue<value)
        {
            i+=value-strtValue;
        }
        else if (strtValue==value)
        {
            pos=i;
            break;
        }
        else
        {
            i=i+(strtValue-value);
        }       
    }

    return pos;
}
kaushik
la source
2
Code non documenté dans une langue avec une convention au moins semi-officielle . En quoi est-ce différent des réponses de John Coleman et Akeshwar, à part interpréter le tag «c» libéralement?
greybeard
3

Voici une solution de style diviser et conquérir. Au détriment de (beaucoup) plus de comptabilité, nous pouvons sauter plus d'éléments; plutôt que de balayer de gauche à droite, testez au milieu et sautez dans les deux sens.

#include <stdio.h>                                                               
#include <math.h>                                                                

int could_contain(int k, int left, int right, int width);                        
int find(int k, int array[], int lower, int upper);   

int main(void){                                                                  
    int a[] = {4,3,2,3,2,3,4,5,4,5,6,7,8,7,8};                                   
    printf("7 first occurs at index %d\n",find(7,a,0,14));                       
    printf("but 9 first \"occurs\" at index %d\n",find(9,a,0,14));               
    return 0;                                                                    
}                                                                                

int could_contain(int k, int left, int right, int width){                        
  return (width >= 0) &&                                                         
         (left <= k && k <= right) ||                                            
         (right <= k && k <= left) ||                                            
         (abs(k - left) + abs(k - right) < width);                               
}                                                                                

int find(int k, int array[], int lower, int upper){                              
  //printf("%d\t%d\n", lower, upper);                                            

  if( !could_contain(k, array[lower], array[upper], upper - lower )) return -1;  

  int mid = (upper + lower) / 2;                                                 

  if(array[mid] == k) return mid;                                                

  lower = find(k, array, lower + abs(k - array[lower]), mid - abs(k - array[mid]));
  if(lower >= 0 ) return lower;                                                    

  upper = find(k, array, mid + abs(k - array[mid]), upper - abs(k - array[upper]));
  if(upper >= 0 ) return upper;                                                  

  return -1;                                                                     

}
Neal Fultz
la source
neal-fultz votre réponse ne retournera pas la première occurrence mais n'importe quelle occurrence aléatoire de l'élément de recherche car vous partez du milieu et sautez de chaque côté.
Ram Patra
Changer l'ordre de récursion est laissé comme un exercice au lecteur.
Neal Fultz
1
neal-fultz alors veuillez éditer le message dans votre appel à la méthode printf ().
Ram Patra
1

const findMeAnElementsFunkyArray = (arr, ele, i) => {
  const elementAtCurrentIndex = arr[i];

  const differenceBetweenEleAndEleAtIndex = Math.abs(
    ele - elementAtCurrentIndex
  );

  const hop = i + differenceBetweenEleAndEleAtIndex;

  if (i >= arr.length) {
    return;
  }
  if (arr[i] === ele) {
    return i;
  }

  const result = findMeAnElementsFunkyArray(arr, ele, hop);

  return result;
};

const array = [4,5,6,5,4,3,2,3,4,5,6,7,8];

const answer = findMeAnElementsFunkyArray(array, 7, 0);

console.log(answer);

Voulait inclure une solution récursive au problème. Prendre plaisir

Anthony Moon Beam Toorie
la source