Supposons qu'on nous donne un tableau de n entiers représentant les cours des actions sur une seule journée. Nous voulons trouver une paire (buyDay, sellDay) , avec buyDay ≤ sellDay , de telle sorte que si nous achetions l'action le buyDay et la vendions le sellDay , nous maximiserions notre profit.
Il existe clairement une solution O (n 2 ) à l'algorithme en essayant toutes les paires possibles (buyDay, sellDay) et en tirant le meilleur de toutes. Cependant, y a-t-il un meilleur algorithme, peut-être un qui s'exécute en temps O (n) ?
arrays
algorithm
big-o
time-complexity
Ajeet Ganga
la source
la source
Réponses:
J'adore ce problème. C'est une question d'entrevue classique et selon la façon dont vous y pensez, vous obtiendrez de meilleures solutions. Il est certainement possible de le faire dans un temps meilleur que O (n 2 ), et j'ai énuméré trois façons différentes de penser au problème ici. J'espère que cela répond à votre question!
Premièrement, la solution diviser pour vaincre. Voyons si nous pouvons résoudre cela en divisant l'entrée en deux, en résolvant le problème dans chaque sous-tableau, puis en combinant les deux ensemble. Il s'avère que nous pouvons réellement le faire et que nous pouvons le faire efficacement! L'intuition est la suivante. Si nous n'avons qu'un seul jour, la meilleure option est d'acheter ce jour-là et de le revendre le même jour sans profit. Sinon, divisez le tableau en deux moitiés. Si nous réfléchissons à ce que pourrait être la réponse optimale, elle doit être dans l'un des trois endroits suivants:
Nous pouvons obtenir les valeurs pour (1) et (2) en invoquant récursivement notre algorithme sur les première et deuxième moitiés. Pour l'option (3), le moyen de réaliser le profit le plus élevé serait d'acheter au point le plus bas du premier semestre et de vendre au point le plus élevé au second semestre. Nous pouvons trouver les valeurs minimum et maximum dans les deux moitiés en effectuant simplement un simple balayage linéaire sur l'entrée et en trouvant les deux valeurs. Cela nous donne alors un algorithme avec la récurrence suivante:
En utilisant le théorème maître pour résoudre la récurrence, nous trouvons que cela s'exécute en temps O (n lg n) et utilisera l'espace O (lg n) pour les appels récursifs. Nous venons de battre la solution naïve O (n 2 )!
Mais attendez! Nous pouvons faire beaucoup mieux que cela. Notez que la seule raison pour laquelle nous avons un terme O (n) dans notre récurrence est que nous avons dû analyser toute l'entrée en essayant de trouver les valeurs minimum et maximum dans chaque moitié. Puisque nous explorons déjà chaque moitié de manière récursive, nous pouvons peut-être faire mieux en faisant en sorte que la récursion rende également les valeurs minimum et maximum stockées dans chaque moitié! En d'autres termes, notre récursivité rend trois choses:
Ces deux dernières valeurs peuvent être calculées de manière récursive en utilisant une récursivité simple que nous pouvons exécuter en même temps que la récursivité à calculer (1):
Si nous utilisons cette approche, notre relation de récurrence est maintenant
L'utilisation du théorème maître nous donne ici un temps d'exécution de O (n) avec un espace O (lg n), ce qui est encore mieux que notre solution originale!
Mais attendez une minute - nous pouvons faire encore mieux que cela! Pensons à résoudre ce problème en utilisant la programmation dynamique. L'idée sera de réfléchir au problème comme suit. Supposons que nous connaissions la réponse au problème après avoir examiné les k premiers éléments. Pourrions-nous utiliser notre connaissance du (k + 1) premier élément, combinée à notre solution initiale, pour résoudre le problème des premiers (k + 1) éléments? Si c'est le cas, nous pourrions obtenir un excellent algorithme en résolvant le problème pour le premier élément, puis les deux premiers, puis les trois premiers, etc. jusqu'à ce que nous l'ayons calculé pour les n premiers éléments.
Réfléchissons à la façon de procéder. Si nous n'avons qu'un seul élément, nous savons déjà qu'il doit s'agir de la meilleure paire achat / vente. Supposons maintenant que nous connaissions la meilleure réponse pour les k premiers éléments et que nous regardions le (k + 1) élément. Ensuite, la seule façon pour cette valeur de créer une solution meilleure que ce que nous avions pour les k premiers éléments est de savoir si la différence entre le plus petit des k premiers éléments et ce nouvel élément est plus grande que la plus grande différence que nous avons calculée jusqu'à présent. Supposons donc qu'en parcourant les éléments, nous gardions une trace de deux valeurs - la valeur minimale que nous avons vue jusqu'à présent et le profit maximal que nous pourrions faire avec les k premiers éléments seulement. Au départ, la valeur minimale que nous avons vue jusqu'à présent est le premier élément et le profit maximal est nul. Quand nous voyons un nouvel élément, Nous mettons d'abord à jour notre profit optimal en calculant ce que nous gagnerions en achetant au prix le plus bas vu jusqu'à présent et en vendant au prix actuel. Si c'est mieux que la valeur optimale que nous avons calculée jusqu'à présent, nous mettons à jour la solution optimale pour être ce nouveau profit. Ensuite, nous mettons à jour l'élément minimum vu jusqu'à présent comme étant le minimum du plus petit élément actuel et du nouvel élément.
Étant donné qu'à chaque étape, nous ne faisons qu'un travail O (1) et que nous visitons chacun des n éléments exactement une fois, cela prend du temps O (n)! De plus, il n'utilise que la mémoire auxiliaire O (1). C'est aussi bon que nous en sommes jusqu'ici!
À titre d'exemple, sur vos entrées, voici comment cet algorithme pourrait fonctionner. Les nombres entre chacune des valeurs du tableau correspondent aux valeurs détenues par l'algorithme à ce point. Vous ne stockeriez pas vraiment tout cela (cela prendrait de la mémoire O (n)!), Mais il est utile de voir l'algorithme évoluer:
Réponse: (5, 10)
Réponse: (4, 12)
Réponse: (1, 5)
Pouvons-nous faire mieux maintenant? Malheureusement, pas dans un sens asymptotique. Si nous utilisons moins de temps O (n), nous ne pouvons pas regarder tous les nombres sur de grandes entrées et ne pouvons donc pas garantir que nous ne manquerons pas la réponse optimale (nous pourrions simplement la «cacher» dans les éléments que nous n'a pas regardé). De plus, nous ne pouvons pas utiliser moins que l'espace O (1). Il peut y avoir des optimisations des facteurs constants cachés dans la notation big-O, mais sinon, nous ne pouvons pas nous attendre à trouver des options radicalement meilleures.
Globalement, cela signifie que nous avons les algorithmes suivants:
J'espère que cela t'aides!
EDIT : Si vous êtes intéressé, j'ai codé une version Python de ces quatre algorithmes afin que vous puissiez jouer avec eux et juger de leurs performances relatives. Voici le code:
la source
C'est le problème de sous-séquence de somme maximale avec un peu d'indirection. Le problème de sous-séquence de somme maximale reçoit une liste d'entiers qui peuvent être positifs ou négatifs, trouvez la plus grande somme d'un sous-ensemble contigu de cette liste.
Vous pouvez facilement convertir ce problème en ce problème en prenant le profit ou la perte entre des jours consécutifs. Ainsi, vous transformeriez une liste de cours boursiers, par exemple
[5, 6, 7, 4, 2]
en une liste de gains / pertes, par exemple[1, 1, -3, -2]
. Le problème de la somme des sous-séquences est alors assez facile à résoudre: Trouvez la sous-séquence avec la plus grande somme d'éléments dans un tableaula source
Je ne sais pas vraiment pourquoi cela est considéré comme une question de programmation dynamique. J'ai vu cette question dans les manuels et les guides d'algorithmes utilisant le runtime O (n log n) et O (log n) pour l'espace (par exemple, Elements of Programming Interviews). Cela semble être un problème beaucoup plus simple que ce que les gens prétendent.
Cela fonctionne en gardant une trace du profit maximum, du prix d'achat minimum et, par conséquent, du prix d'achat / de vente optimal. En parcourant chaque élément du tableau, il vérifie si l'élément donné est plus petit que le prix d'achat minimum. Si tel est le cas, l'indice de prix d'achat minimum, (
min
), est mis à jour pour être l'indice de cet élément. De plus, pour chaque élément, l'becomeABillionaire
algorithme vérifie siarr[i] - arr[min]
(la différence entre l'élément actuel et le prix d'achat minimum) est supérieure au profit actuel. Si tel est le cas, le profit est mis à jour en fonction de cette différence et acheter est défini surarr[min]
et vendre est défini surarr[i]
.Fonctionne en un seul passage.
Co-auteur: https://stackoverflow.com/users/599402/ephraim
la source
Le problème est identique à la sous-séquence maximale que
je l'ai résolu en utilisant la programmation dynamique. Gardez une trace de l'actuel et du précédent (bénéfice, date d'achat et date de vente) Si le courant est plus élevé que le précédent, remplacez le précédent par le courant.
la source
voici ma solution Java:
la source
J'ai trouvé une solution simple - le code est plus explicite. C'est l'une de ces questions de programmation dynamique.
Le code ne prend pas en charge la vérification des erreurs et les cas extrêmes. C'est juste un exemple pour donner l'idée de la logique de base pour résoudre le problème.
la source
Voici ma solution. modifie l'algorithme de sous-séquence maximum. Résout le problème en O (n). Je pense que cela ne peut pas être fait plus rapidement.
la source
C'est un problème intéressant, car il semble difficile, mais une réflexion approfondie aboutit à une solution élégante et épurée.
Comme cela a été noté, il peut être résolu par force brute en temps O (N ^ 2). Pour chaque entrée du tableau (ou de la liste), parcourez toutes les entrées précédentes pour obtenir le minimum ou le maximum selon que le problème est de trouver le gain ou la perte le plus élevé.
Voici comment réfléchir à une solution en O (N): chaque entrée représente un nouveau max (ou min) possible. Ensuite, tout ce que nous devons faire est de sauvegarder le min (ou max) précédent, et de comparer le diff avec le courant et le min (ou max) précédent. Peasy facile.
Voici le code, en Java comme test JUnit:
Dans le cas du calcul de la plus grande perte, nous gardons une trace du maximum dans la liste (prix d'achat) jusqu'à l'entrée actuelle. Nous calculons ensuite la différence entre le max et l'entrée courante. Si max - current> maxLoss, alors nous gardons ce diff comme le nouveau maxLoss. Puisque l'indice de max est garanti inférieur à l'indice actuel, nous garantissons que la date «d'achat» est inférieure à la date de «vente».
Dans le cas du calcul du gain le plus élevé, tout est inversé. Nous gardons une trace du min dans la liste jusqu'à l'entrée actuelle. Nous calculons le diff entre le min et l'entrée courante (en inversant l'ordre dans la soustraction). Si current - min> maxGain, nous conservons cette différence comme nouveau maxGain. Encore une fois, l'indice de «acheter» (min) vient avant l'indice de courant («vendre»).
Nous avons seulement besoin de garder une trace du maxGain (ou maxLoss), et de l'indice de min ou max, mais pas les deux, et nous n'avons pas besoin de comparer les indices pour valider que `` acheter '' est inférieur à `` vendre '', car nous obtenez cela naturellement.
la source
Bénéfice maximal de vente unique, solution O (n)
Voici un projet qui effectue des tests de complexité temporelle sur les approches o (N) vs o (n ^ 2) sur un ensemble de données aléatoires sur 100k pouces. O (n ^ 2) prend 2 secondes, tandis que O (n) prend 0,01 s
https://github.com/gulakov/complexity.js
C'est l'approche la plus lente, o (n ^ 2) qui parcourt le reste des jours pour chaque jour, double boucle.
la source
La réponse aux votes les plus élevés ne permet pas les cas dans lesquels le profit maximum est négatif et devrait être modifiée pour permettre de tels cas. On peut le faire en limitant la plage de la boucle à (len (a) - 1) et en modifiant la manière dont le profit est déterminé en décalant l'indice de un.
Comparez cette version de la fonction avec la précédente pour le tableau:
la source
la source
Une possibilité pour déterminer le profit maximum pourrait être de garder une trace des éléments minimum du côté gauche et maximum du côté droit dans le tableau à chaque index du tableau. Lorsque vous parcourez ensuite les cours des actions, pour un jour donné, vous connaîtrez le prix le plus bas jusqu'à ce jour, et vous connaîtrez également le prix maximum après (et y compris) ce jour-là.
Par exemple, définissons un
min_arr
etmax_arr
, avec le tableau donnéarr
. Indexi
inmin_arr
serait l'élément minimal inarr
pour tous les indices<= i
(à gauche et y compris i). Indexi
inmax_arr
serait l'élément maximum inarr
pour tous les indices>= i
(à droite et y compris i). Ensuite, vous pourriez trouver la différence maximale entre les éléments correspondants dansmax_arr
et `min_arr ':Cela devrait fonctionner dans le temps O (n), mais je pense que cela utilise beaucoup d'espace.
la source
C'est la différence maximale entre deux éléments du tableau et voici ma solution:
Complexité temporelle O (N) Complexité spatiale O (1)
la source
Après avoir échoué à un examen de codage en direct pour un poste d'ingénieur en solutions FB, j'ai dû le résoudre dans une atmosphère calme et fraîche, alors voici mes 2 cents:
la source
La seule réponse qui répond vraiment à la question est celle de @akash_magoon (et de manière si simple!), Mais elle ne renvoie pas l'objet exact spécifié dans la question. J'ai un peu refacturé et j'ai ma réponse en PHP renvoyant exactement ce qui est demandé:
la source
Une solution soignée:
la source
Ce programme en python3 peut renvoyer le prix d'achat et le prix de vente qui maximiseront le profit, calculé avec la complexité temporelle de O (n) et la complexité spatiale de O (1) .
la source
Voici ma solution
la source
Pour toutes les réponses gardant une trace des éléments minimum et maximum, cette solution est en fait une solution O (n ^ 2). En effet, à la fin, il faut vérifier si le maximum s'est produit après le minimum ou non. Dans le cas où ce ne serait pas le cas, d'autres itérations sont nécessaires jusqu'à ce que cette condition soit remplie, ce qui laisse le pire des cas de O (n ^ 2). Et si vous voulez sauter les itérations supplémentaires, il faut beaucoup plus d'espace. Quoi qu'il en soit, un non-non par rapport à la solution de programmation dynamique
la source