Qual é o problema da subsequência comum mais longa?
As mais longas subsequências comuns (LCS) são um problema típico em TI . As abordagens para resolver o problema LCS aparecem com frequência em entrevistas para programadores e cursos de algoritmos.
Qual é a mais longa subsequência comum?
O objetivo é encontrar a maior subsequência comum em duas sequências. É aqui que uma subsequência é derivada de uma sequência. A subsequência tem a mesma ordem elementar, em alguns casos, quando os elementos são removidos. Vamos dar uma olhada em alguns exemplos para ter uma ideia melhor do princípio:
Sequence X |
Sequence Y |
LCS(X, Y) |
---|---|---|
FATHER
|
MATTER
|
ATER
|
MOTHER
|
HERBAL
|
HER
|
DAVID
|
DANIEL
|
DAI
|
Há uma importante diferença entre a mais longa subsequência comum e a mais longa substring comum sendo que uma substring pode não ter lacunas. No exemplo de “DAVID” e “DANIEL”, a substring comum mais longa seria “DA”, pois “I” está separado por “V” e “N”.
Há algum exemplo prático do problema LCS?
O problema da mais longa subsequência comum é uma questão importante em todas as áreas em que são usadas sequências que se originam umas das outras. Existem algumas maneiras de encontrar LCS para verificar se há semelhanças ou diferenças e, por sua vez, descobrir qualquer plágio. O conhecido utilitário “diff”, que verifica se há alterações nos arquivos de texto de origem, baseia-se no problema de LCS.
Na bioinformática, o problema da mais longa subsequência comum é usado com frequência ao analisar sequências de DNA. As bases do DNA mudam em determinadas posições ao longo do tempo devido a mutações. A presença de uma longa subsequência comum em duas sequências sugere uma relação genética. Isso pode nos permitir reconstituir a evolução entre as espécies graças ao desenvolvimento genético.
Os linguistas também podem usar o problema da mais longa subsequência comum para pesquisar o desenvolvimento linguístico ao longo dos séculos. Se você tiver duas palavras de idiomas diferentes que tenham o mesmo significado e uma longa sequência comum, isso sugere que elas têm a mesma raiz e etimologia. Isso seria considerado, então, um desenvolvimento histórico semelhante.
Quais são as soluções para o problema da mais longa subsequência comum?
Em primeiro lugar, é possível resolver o problema da LCS com uma abordagem ingênua. Essa é uma abordagem simples, sem usar nenhuma otimização ou método especial. Para isso, basta computar todas as subsequências de duas sequências e encontrar a subsequência comum mais longa. Infelizmente, essa abordagem não é muito eficiente e, portanto, só é adequada para sequências curtas.
Abaixo você encontrará três soluções eficientes para o problema LCS:
- Abordagem recursiva
- Otimização por meio de memoização
- Programação dinâmica
O que todas as abordagens têm em comum é que, com relação às duas sequências, elas diferem em três casos:
- A última letra é a mesma.
- A última letra não é a mesma.
- O comprimento de uma das sequências é zero.
As abordagens diferem em sua complexidade de tempo (tempo de execução assintótico) e complexidade de espaço (uso de memória):
Abordagem | Tempo de execução | Memória |
---|---|---|
Abordagem ingênua | O(n * n²)
|
O(n)
|
Abordagem recursiva | O(n²)
|
O(1)
|
Otimização via memoização | O(n *m)
|
O(n* m)
|
Programação dinâmica | O(n *m)
|
O(n* m)
|
Todos os algoritmos definidos abaixo calculam o comprimento da maior subsequência comum. Se for o caso, há vários conjuntos de subsequências com esse comprimento que podem ser determinados por meio de outras etapas.
Determinação recursiva da maior subsequência comum
Se você observar o problema LCS, verá que ele tem uma “subestrutura ideal”. Isso significa que o problema pode ser reduzido a subproblemas. Como solução, você pode usar uma abordagem recursiva. Abaixo, você encontrará alguns exemplos de algoritmos para implementar isso em três das linguagens de programação mais comuns.
Python
Java
C++
Otimização da abordagem recursiva usando memoização
Ao analisar novamente a abordagem recursiva, você pode ver que as partes sobrepostas são computadas. Essas características, chamadas de “subproblemas sobrepostos”, são conhecidas das sequências de Fibonacci. Nesse caso também as sequências recursivas são constantemente computadas para obter uma solução. Para tornar o processo mais eficiente, vale a pena usar a memoização. Em outras palavras, você pode armazenar os subproblemas que já foram computados em uma matriz bidimensional.