Suponha que recebamos uma matriz de n números inteiros representando os preços das ações em um único dia. Queremos encontrar um par (buyDay, sellDay) , com buyDay ≤ sellDay , de modo que, se comprássemos as ações na buyDay e as vendêss na sellDay , maximizaríamos nosso lucro.
Claramente, existe uma solução O (n 2 ) para o algoritmo, testando todos os pares possíveis (buyDay, sellDay) e tirando o melhor proveito de todos eles. No entanto, existe um algoritmo melhor, talvez um que seja executado em O (n) tempo?
arrays
algorithm
big-o
time-complexity
Ajeet Ganga
fonte
fonte
Respostas:
Eu amo esse problema. É uma pergunta clássica de entrevista e, dependendo de como você pensa sobre isso, acabará obtendo soluções cada vez melhores. Certamente é possível fazer isso em um tempo melhor que O (n 2 ), e listei três maneiras diferentes pelas quais você pode pensar sobre o problema aqui. Espero que isso responda sua pergunta!
Primeiro, a solução de dividir e conquistar. Vamos ver se conseguimos resolver isso dividindo a entrada pela metade, resolvendo o problema em cada sub-matriz e combinando as duas. Acontece que podemos fazer isso de maneira eficiente! A intuição é a seguinte. Se tivermos um único dia, a melhor opção é comprar nesse dia e depois revendê-lo no mesmo dia sem fins lucrativos. Caso contrário, divida a matriz em duas metades. Se pensarmos sobre qual pode ser a resposta ideal, ela deve estar em um dos três lugares:
Podemos obter os valores para (1) e (2) invocando recursivamente nosso algoritmo na primeira e na segunda metades. Para a opção (3), o caminho para obter o maior lucro seria comprar no ponto mais baixo do primeiro semestre e vender no ponto mais alto do segundo semestre. Podemos encontrar os valores mínimo e máximo nas duas metades, basta fazer uma varredura linear simples sobre a entrada e encontrar os dois valores. Isso nos fornece um algoritmo com a seguinte recorrência:
Usando o Teorema Mestre para resolver a recorrência, descobrimos que isso é executado no tempo O (n lg n) e utilizará o espaço O (lg n) para as chamadas recursivas. Acabamos de vencer a solução ingênua de O (n 2 )!
Mas espere! Podemos fazer muito melhor que isso. Observe que a única razão pela qual temos um termo O (n) em nossa recorrência é que tivemos que varrer toda a entrada tentando encontrar os valores mínimo e máximo em cada metade. Como já estamos explorando recursivamente cada metade, talvez possamos fazer melhor com a recursão também devolvendo os valores mínimo e máximo armazenados em cada metade! Em outras palavras, nossa recursão devolve três coisas:
Esses dois últimos valores podem ser computados recursivamente usando uma recursão direta que podemos executar ao mesmo tempo que a recursão para calcular (1):
Se usarmos essa abordagem, nossa relação de recorrência será agora
Usar o Teorema Mestre aqui nos fornece um tempo de execução de O (n) com O (lg n), o que é ainda melhor que a nossa solução original!
Mas espere um minuto - podemos fazer ainda melhor do que isso! Vamos pensar em resolver esse problema usando programação dinâmica. A idéia será pensar sobre o problema da seguinte maneira. Suponha que soubéssemos a resposta para o problema depois de examinar os primeiros k elementos. Poderíamos usar nosso conhecimento do (k + 1) st elemento, combinado com a nossa solução inicial, para resolver o problema dos primeiros (k + 1) elementos? Nesse caso, poderíamos obter um ótimo algoritmo resolvendo o problema do primeiro elemento, depois dos dois primeiros, depois dos três primeiros, etc. até calculá-lo para os primeiros n elementos.
Vamos pensar em como fazer isso. Se tivermos apenas um elemento, já sabemos que esse deve ser o melhor par de compra / venda. Agora, suponha que sabemos a melhor resposta para os primeiros k elementos e olhemos para o (k + 1) st elemento. Então, a única maneira de esse valor criar uma solução melhor do que a que possuíamos para os primeiros k elementos é se a diferença entre o menor dos primeiros elementos k e o novo elemento for maior que a maior diferença que computamos até agora. Portanto, suponha que, ao percorrermos os elementos, controlemos dois valores - o valor mínimo que vimos até agora e o lucro máximo que poderíamos obter com apenas os primeiros k elementos. Inicialmente, o valor mínimo que vimos até agora é o primeiro elemento e o lucro máximo é zero. Quando vemos um novo elemento, primeiro atualizamos nosso lucro ideal calculando quanto ganharíamos comprando pelo preço mais baixo visto até agora e vendendo pelo preço atual. Se isso for melhor do que o valor ideal que calculamos até agora, atualizamos a solução ideal para obter esse novo lucro. Em seguida, atualizamos o elemento mínimo visto até o momento como o mínimo do menor elemento atual e o novo elemento.
Como em cada etapa, fazemos apenas O (1) trabalho e visitamos cada um dos n elementos exatamente uma vez, isso leva O (n) tempo para ser concluído! Além disso, ele usa apenas armazenamento auxiliar O (1). Isso é tão bom quanto chegamos até agora!
Como exemplo, em suas entradas, veja como esse algoritmo pode ser executado. Os números entre cada um dos valores da matriz correspondem aos valores mantidos pelo algoritmo naquele momento. Na verdade, você não armazenaria tudo isso (seria necessário memória O (n)!), Mas é útil ver o algoritmo evoluir:
Resposta: (5, 10)
Resposta: (4, 12)
Resposta: (1, 5)
Podemos fazer melhor agora? Infelizmente, não em um sentido assintótico. Se usarmos menos de O (n) tempo, não podemos olhar para todos os números em entradas grandes e, portanto, não podemos garantir que não perderemos a resposta ideal (poderíamos simplesmente "ocultá-la" nos elementos que não olhou). Além disso, não podemos usar menos que O (1) espaço. Pode haver algumas otimizações para os fatores constantes ocultos na notação big-O, mas, caso contrário, não podemos esperar encontrar opções radicalmente melhores.
No geral, isso significa que temos os seguintes algoritmos:
Espero que isto ajude!
Edição : Se você estiver interessado, eu codifiquei uma versão Python desses quatro algoritmos para que você possa brincar com eles e julgar seu desempenho relativo. Aqui está o código:
fonte
Esse é o problema de subsequência da soma máxima com um pouco de indireção. O problema da subsequência da soma máxima recebe uma lista de números inteiros que podem ser positivos ou negativos; encontre a maior soma de um subconjunto contíguo dessa lista.
Você pode converter esse problema trivialmente para esse problema obtendo lucros ou perdas entre dias consecutivos. Então você transformaria uma lista de preços das ações, por exemplo,
[5, 6, 7, 4, 2]
em uma lista de ganhos / perdas, por exemplo[1, 1, -3, -2]
,. O problema da soma de subsequência é muito fácil de resolver: encontre a subsequência com a maior soma de elementos em uma matrizfonte
Não sei ao certo por que isso é considerado uma questão de programação dinâmica. Eu já vi essa pergunta em livros didáticos e guias de algoritmos usando O (n log n) runtime e O (log n) como espaço (por exemplo, elementos de entrevistas de programação). Parece um problema muito mais simples do que as pessoas estão fazendo parecer.
Isso funciona acompanhando o lucro máximo, o preço mínimo de compra e, consequentemente, o preço ótimo de compra / venda. À medida que percorre cada elemento da matriz, ele verifica se o elemento fornecido é menor que o preço mínimo de compra. Se for, o índice mínimo de preço de compra (
min
) será atualizado para ser o índice desse elemento. Além disso, para cada elemento, obecomeABillionaire
algoritmo verifica searr[i] - arr[min]
(a diferença entre o elemento atual e o preço mínimo de compra) é maior que o lucro atual. Se for, o lucro é atualizado para essa diferença e a compra é definida comoarr[min]
e a venda é definida comoarr[i]
.É executado em um único passe.
Co-autor: https://stackoverflow.com/users/599402/ephraim
fonte
O problema é idêntico à subseqüência máxima que
eu resolvi usando a programação dinâmica. Acompanhe o atual e o anterior (data de lucro, data de compra e venda) Se a corrente for maior que a anterior, substitua a anterior por atual.
fonte
aqui está a minha solução Java:
fonte
Eu vim com uma solução simples - o código é mais auto-explicativo. É uma daquelas questões de programação dinâmica.
O código não cuida da verificação de erros e dos casos extremos. É apenas uma amostra para dar a idéia da lógica básica para resolver o problema.
fonte
Aqui está a minha solução. modifica o algoritmo máximo de sub-sequência. Resolve o problema em O (n). Eu acho que não pode ser feito mais rápido.
fonte
Esse é um problema interessante, porque parece difícil, mas um pensamento cuidadoso produz uma solução elegante e simplificada.
Como foi observado, ele pode ser resolvido com força bruta no tempo O (N ^ 2). Para cada entrada na matriz (ou lista), repita todas as entradas anteriores para obter o mínimo ou o máximo, dependendo se o problema é encontrar o maior ganho ou perda.
Veja como pensar em uma solução em O (N): cada entrada representa um novo máximo possível (ou min). Então, tudo o que precisamos fazer é salvar o mínimo anterior (ou máximo) e comparar o diff com o atual e o mínimo anterior (ou máximo). Mole-mole.
Aqui está o código, em Java, como um teste JUnit:
No caso de calcular a maior perda, acompanhamos o máximo na lista (preço de compra) até a entrada atual. Em seguida, calculamos a diferença entre a entrada máxima e a atual. Se max - current> maxLoss, mantemos essa diferença como o novo maxLoss. Como o índice max é garantido inferior ao índice atual, garantimos que a data de compra é menor que a data de venda.
No caso de calcular o maior ganho, tudo é revertido. Nós acompanhamos os minutos na lista até a entrada atual. Calculamos a diferença entre a entrada mínima e a entrada atual (revertendo a ordem na subtração). Se current - min> maxGain, mantemos essa diferença como o novo maxGain. Novamente, o índice de 'compra' (min) vem antes do índice de corrente ('venda').
Precisamos apenas acompanhar o maxGain (ou maxLoss) e o índice de min ou max, mas não ambos, e não precisamos comparar índices para validar que 'comprar' é menor que 'vender', pois pegue isso naturalmente.
fonte
Lucro máximo de venda única, solução O (n)
Aqui está um projeto que faz testes de complexidade de tempo em abordagens o (N) vs o (n ^ 2) em um conjunto de dados aleatórios em 100k ints. O (n ^ 2) leva 2 segundos, enquanto O (n) leva 0,01s
https://github.com/gulakov/complexity.js
Essa é a abordagem mais lenta, o (n ^ 2), que faz um loop pelo resto dos dias de cada dia, em loop duplo.
fonte
A resposta mais votada não permite casos em que o lucro máximo é negativo e deve ser modificado para permitir tais casos. Pode-se fazer isso limitando o intervalo do loop para (len (a) - 1) e alterando a maneira como o lucro é determinado deslocando o índice por um.
Compare esta versão da função com a anterior para a matriz:
fonte
fonte
Uma possibilidade de determinar o lucro máximo pode ser manter o controle dos elementos mínimo esquerdo e máximo direito do array em cada índice do array. Quando você estiver percorrendo os preços das ações, em qualquer dia, você saberá o preço mais baixo até aquele dia e também saberá o preço máximo após (e incluindo) naquele dia.
Por exemplo, vamos definir a
min_arr
emax_arr
, com a matriz fornecidaarr
. O índicei
emmin_arr
seria o elemento mínimoarr
para todos os índices<= i
(à esquerda de e incluindo i). O índicei
emmax_arr
seria o elemento máximoarr
para todos os índices>= i
(direito de e incluindo i). Então, você pode encontrar a diferença máxima entre os elementos correspondentes emmax_arr
e `min_arr ':Isso deve ser executado em O (n) tempo, mas acredito que consome muito espaço.
fonte
Essa é a diferença máxima entre dois elementos na matriz e esta é a minha solução:
O (N) complexidade do tempo O (1) complexidade do espaço
fonte
Depois de falhar isso em um exame de codificação ao vivo para uma posição de engenheiro de soluções da FB, tive que resolvê-lo em uma atmosfera calma e fria, então aqui estão meus 2 centavos:
fonte
A única resposta que realmente responde à pergunta é a de @akash_magoon (e de uma maneira tão simples!), Mas ela não retorna o objeto exato especificado na pergunta. Refatorei um pouco e tenho minha resposta em PHP retornando exatamente o que é solicitado:
fonte
Uma solução elegante:
fonte
Este programa em python3 pode retornar o preço de compra e o preço de venda que maximizarão o lucro, calculado com a complexidade de tempo de O (n) e a complexidade de espaço de O (1) .
fonte
Aqui está a minha solução
fonte
Para todas as respostas que acompanham os elementos mínimo e máximo, essa solução é realmente uma solução O (n ^ 2). Isso ocorre porque, no final, deve-se verificar se o máximo ocorreu após o mínimo ou não. Caso contrário, são necessárias mais iterações até que essa condição seja atendida, e isso deixa o pior caso de O (n ^ 2). E se você quiser pular as iterações extras, será necessário muito mais espaço. De qualquer forma, um não-não, em comparação com a solução de programação dinâmica
fonte