Alors vous voilà. Soulagé. Épuisé. Vous avez finalement trouvé une approche pour résoudre la question de codage délicate que votre intervieweur vous pose. Vous l'avez peut-être même écrit sur le tableau blanc, ligne par ligne. Et vous avez passé du bon temps! Vous n'êtes qu'à 20 minutes de la réunion. Votre interlocuteur doit être impressionné.
Droite?
"Cela fonctionnera, mais des idées sur la façon de le faire plus efficacement?"
Ton coeur s'enfonce. Vous pensiez en avoir terminé avec la conception complexe des algorithmes! Vous essayez de trouver d'autres moyens de résoudre le problème, mais vous ne pouvez penser qu'à l'approche que vous avez déjà mise au point.
Cela arrive à presque tout le monde. Et ce n'est pas parce qu'ils sont stupides. C'est parce que la plupart des gens ne disposent pas de méthode pour améliorer l'efficacité de leurs algorithmes.
Mais la vérité est qu'il y en a beaucoup. La prochaine fois que vous serez perplexe, essayez d’appliquer ces trois approches communes.
1. Utilisez une carte de hachage
C'est vrai. Les cartes de hachage / tableaux associatifs / dictionnaires (ils portent plusieurs noms, selon le langage de programmation que vous utilisez) ont une capacité magique à réduire le temps d'exécution des algorithmes.
Par exemple, supposons que la question visait à trouver le nombre le plus répété dans un tableau de nombres.
Votre première pensée pourrait être de sauter dans certaines boucles. Pour chacun de nos chiffres, calculez son nombre et voyez si c'est le plus gros. Comment pouvons-nous obtenir le nombre pour chaque nombre? Parcourez le tableau en comptant le nombre de fois qu'il se produit! Nous parlons donc de deux boucles imbriquées. En pseudocode:
def get_mode (nums): max_count = 0 mode = null pour potenti_mode dans nums: count = 0 pour nombre dans our_array: count + = 1 si count> = max_count: mode = potenti_mode max_count = count return
Pour le moment, nous parcourons notre tableau entier une fois pour chaque élément du tableau, mais nous pouvons faire mieux. En grosse notation O, c'est le temps O (n 2 ) au total.
Si nous stockons nos comptes dans une carte de hachage (mappage des nombres sur leurs comptes), nous pouvons résoudre le problème en une seule promenade dans le tableau (temps O (n)!):
def get_mode (nums): max_count = 0 mode = null compte = nouveau HashMap, commençant chaque valeur à 0 pour potenti_mode dans nums: compte + = 1 si compte> max_count: mode = mode_messeur max_count = compte retour
Plus vite!
2. Utiliser la manipulation de bits
Cela vous distinguera vraiment du peloton. Cela ne s'applique pas à tous les problèmes, mais si vous gardez cela dans votre poche et que vous vous en débarrassez au bon moment, vous aurez l'air d'une rockstar.
Voici un exemple: supposons que nous ayons un tableau de nombres, où chaque nombre apparaît deux fois, à l’exception d’un nombre qui n’apparaît qu’une fois. Nous écrivons une fonction pour trouver le nombre solitaire et non répété.
Votre premier instinct pourrait être d'utiliser une carte de hachage, puisque nous venons d'en parler. C'est un bon instinct à avoir! Et ça va marcher pour celui-ci. Nous pouvons créer une carte de «dénombrement» très similaire et l'utiliser pour voir quel nombre se termine par un décompte de 1.
Mais il y a un moyen encore meilleur. Si vous êtes familier avec la manipulation de bits, vous connaissez peut-être XOR. Une chose spéciale à propos de XOR est que si vous XOR un nombre avec lui-même, les bits sont «annulés» à 0. Pour ce problème, si nous xorons tous les nombres du tableau, nous nous retrouverons avec le numéro qui n'a pas ne pas annuler:
def find_unrepeated (nums): unrepeated = 0 pour num en nums: unrepeated = unrepeated XOR num return non repeated
3. Aller de bas en haut
Écrivez une fonction qui donne le «nième» nombre de Fibonacci, à partir du nombre n. Celui-ci est un classique, et il se prête très bien à la récursion:
def fib (n): si n vaut 0 ou 1: retourne 1 retourne fib (n-1) + fib (n-2)
Mais la réponse récursive simple n'est pas la seule! Réfléchissez bien à ce que cette fonction fait. Supposons que n soit 5. Pour obtenir la réponse, il appelle récursivement fib (4) et fib (3). Maintenant, que fait cet appel à fib (4)? Il appelle fib (3) et fib (2). Mais nous venons de dire que nous avions déjà reçu un appel à fib (3)! Cette fonction récursive mignonne fait beaucoup de travail répété. Le coût total en temps s'avère être O (2 n ). C'est mauvais - bien pire que O (n 2 ).
Au lieu de naître récursivement à 1, passons de «bas en haut», de 1 à n. Cela nous permet de sauter la récursivité:
def fib (n): previous = 0 previous_previous = 1 pour i dans l'intervalle 1 à n: current = précédent + précédent_previous précédent_previous = précédent précédent = actuel actuel
Le code est plus long, mais c'est beaucoup plus efficace! Jusqu'à O (n) fois. En plus des algorithmes de déroulement récursifs, nous économisons de la place. Tous ces appels récursifs s'accumulent dans la pile d'appels, qui reste en mémoire et compte pour notre coût en espace. Notre fonction récursive avait un coût en espace O (n), mais cette fonction itérative prend un espace en O (1).
La prochaine fois que votre interlocuteur vous demandera d'améliorer l'efficacité de votre solution, essayez de parcourir ces stratégies et de voir si elles vous aident. Avec suffisamment de pratique, vous allez probablement passer directement à la solution optimisée, en évitant la solution plus naïve. Et c'est une bonne chose. Cela ne signifie pas seulement que vous devenez un meilleur intervieweur - cela signifie que vous devenez un meilleur ingénieur.