Pular para o conteudo principal

Maior subsequência comum

Como transformar a comparação entre duas strings em uma tabela de subproblemas em vez de repetir escolhas de recursao.

Dadas duas strings text1 e text2, retorne o comprimento da maior subsequencia comum entre elas.

Se não existir subsequencia comum, retorne 0.

Exemplo:

Input:  text1 = "abcde", text2 = "ace"
Output: 3

A maior subsequencia comum e "ace", com comprimento 3.

O que perceber antes de codar

Erros comuns

Passo 1 - Não confundir subsequencia com substring

Aqui pode pular caracteres.

Isso significa que "ace" e subsequencia de "abcde", mesmo sem estar colada.

Se você pensar como substring, vai montar a solução errada.

Passo 2 - Escrever a versão inicial recursiva

A versão inicial natural e:

  • olhar o primeiro caractere dos dois lados
  • se forem iguais, conta 1 e anda nos dois
  • se forem diferentes, tenta pular um lado ou o outro

Funciona.

Mas repete muitos subproblemas.

Passo 3 - Definir o estado certo

Uma definição boa e:

  • dp[i][j] = melhor resposta usando os primeiros i caracteres de text1 e os primeiros j de text2

Isso ajuda porque cada celula depende de subproblemas menores e bem definidos.

Passo 4 - Descobrir a transição

Se os ultimos caracteres batem:

  • dp[i][j] = dp[i - 1][j - 1] + 1

Se não batem:

  • dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

Essa e a regra central da tabela.

O editor interativo precisa de JavaScript. Voce ainda pode ler o desafio e copiar o codigo inicial abaixo.

Codigo inicial

export function longestCommonSubsequence(text1: string, text2: string): number {
  // sua solução aqui
  return 0
}
solution.ts
Travado? Dicas disponiveis.

Ainda não resolveu?

Ver a solução agora pode reduzir o aprendizado. Vale a pena tentar mais um pouco.

Abrir a solucao de referencia

Sem JavaScript, a solucao de referencia aparece inline em vez de um dialogo.

Solução

Complexidade final

Tempo: O(m * n)

Espaço: O(m * n)

Solução 1 - Recursao pura

Boa versão inicial para enxergar a lógica de escolha.

export function longestCommonSubsequence(text1: string, text2: string): number {
  function dfs(index1: number, index2: number): number {
    if (index1 === text1.length || index2 === text2.length) {
      return 0
    }

    if (text1[index1] === text2[index2]) {
      return 1 + dfs(index1 + 1, index2 + 1)
    }

    return Math.max(dfs(index1 + 1, index2), dfs(index1, index2 + 1))
  }

  return dfs(0, 0)
}

Funciona, mas explode em retrabalho.

A etapa intermediaria seria memorizacao: guardar cada par (index1, index2) para não resolver o mesmo subproblema de novo.

Solução 2 - Programacao dinamica 2D

Agora cada subproblema e resolvido uma vez.

export function longestCommonSubsequence(text1: string, text2: string): number {
  const rows = text1.length + 1
  const cols = text2.length + 1
  const dp = Array.from({ length: rows }, () => new Array(cols).fill(0))

  for (let i = 1; i < rows; i += 1) {
    for (let j = 1; j < cols; j += 1) {
      if (text1[i - 1] === text2[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
      }
    }
  }

  return dp[text1.length][text2.length]
}

O insight forte aqui e perceber que a recursao já estava te dando a formula. A programacao dinamica só organiza isso em tabela.

O que dizer na entrevista

Uma explicação curta e boa seria:

A versão inicial recursiva compara os caracteres atuais. Se batem, eu conto 1 e ando nos dois. Se não batem, tento pular um lado ou o outro. O problema e o retrabalho. Entao eu transformo isso em programação dinamica 2D, onde dp[i][j] guarda a melhor resposta para os primeiros i e j caracteres.

Isso mostra que você:

  • separou bem subsequencia de substring
  • conectou a recursão com a tabela
  • explicou a transição sem decorar formula solta

Quando esse padrão aparece de novo

Esse padrão reaparece quando você precisa:

  • comparar duas sequencias ao mesmo tempo
  • preencher tabela 2D de subproblemas
  • traduzir recursao com escolhas em programacao dinamica iterativa

O ponto não e decorar esse problema.

O ponto e aprender a modelar subproblemas em duas dimensoes.

Proximo desafio Matriz espiral Desafio anterior Rotacionar imagem

Desafios relacionados

Artigos relacionados