feat: new tutorials added.

This commit is contained in:
2026-04-07 23:02:31 -03:00
parent 9b0d2d9fd2
commit 47a0a1f3cf
16 changed files with 354 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,51 @@
\documentclass[10pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{fullpage}
\usepackage{url}
\pagenumbering{gobble}
\usepackage{hyperref}
\title{ Tutorial: Distância de Edição}
\author{}
\date{}
\begin{document}
\maketitle
\section{Solução do Problema}
O problema de encontrar o menor número de operações para transformar uma string em outra, classicamente conhecido como Distância de Edição (\textit{Edit Distance}), pode ser resolvido de forma eficiente por meio de \textbf{programação dinâmica}.
\subsection{Definição do Subproblema}
Sejam $w_1$ e $w_2$ as duas strings de tamanhos $N$ e $M$, respectivamente.
Definimos o nosso estado da programação dinâmica como:
$$dp[i][j] = \text{número mínimo de operações para transformar o sufixo } w_1[i \dots N-1] \text{ no sufixo } w_2[j \dots M-1].$$
Assim, $dp[0][0]$ representará a resposta final para as duas strings inteiras.
\subsection{Função de Transição}
Para determinar $dp[i][j]$, olhamos para os caracteres atuais $w_1[i]$ e $w_2[j]$ e tomamos uma decisão.
Se os caracteres forem iguais ($w_1[i] == w_2[j]$), não precisamos realizar nenhuma operação. Apenas avançamos na análise de ambas as strings:
$$dp[i][j] = dp[i+1][j+1]$$
Se os caracteres forem diferentes ($w_1[i] \neq w_2[j]$), temos três opções de edição. Avaliamos o resultado de aplicar cada uma delas e escolhemos a que produz o menor custo final. Cada operação tem um custo de $1$:
$$dp[i][j] = 1 + \min \begin{cases}
dp[i+1][j] & \text{(Remoção do caractere } w_1[i]\text{)} \\
dp[i+1][j+1] & \text{(Substituição de } w_1[i] \text{ por } w_2[j]\text{)} \\
dp[i][j+1] & \text{(Inserção do caractere } w_2[j] \text{ em } w_1\text{)}
\end{cases}$$
\subsection{Casos Base}
Os casos base ocorrem quando esgotamos os caracteres de uma das strings (ou seja, quando $i$ ou $j$ ultrapassam os limites):
Se consumirmos toda a string $w_1$ ($i \ge N$), mas ainda restarem caracteres em $w_2$, a única forma de igualá-las é \textbf{inserir} todos os $M - j$ caracteres restantes:
$$dp[N][j] = M - j$$
Da mesma forma, se consumirmos toda a string $w_2$ ($j \ge M$), mas ainda restarem caracteres em $w_1$, a única forma de igualá-las é \textbf{remover} todos os $N - i$ caracteres que sobraram:
$$dp[i][M] = N - i$$\end{document}

Binary file not shown.

View File

@@ -0,0 +1,38 @@
\section{Solução do Problema}
O problema de encontrar o menor número de operações para transformar uma string em outra, classicamente conhecido como Distância de Edição (\textit{Edit Distance}), pode ser resolvido de forma eficiente por meio de \textbf{programação dinâmica}.
\subsection{Definição do Subproblema}
Sejam $w_1$ e $w_2$ as duas strings de tamanhos $N$ e $M$, respectivamente.
Definimos o nosso estado da programação dinâmica como:
$$dp[i][j] = \text{número mínimo de operações para transformar o sufixo } w_1[i \dots N-1] \text{ no sufixo } w_2[j \dots M-1].$$
Assim, $dp[0][0]$ representará a resposta final para as duas strings inteiras.
\subsection{Função de Transição}
Para determinar $dp[i][j]$, olhamos para os caracteres atuais $w_1[i]$ e $w_2[j]$ e tomamos uma decisão.
Se os caracteres forem iguais ($w_1[i] == w_2[j]$), não precisamos realizar nenhuma operação. Apenas avançamos na análise de ambas as strings:
$$dp[i][j] = dp[i+1][j+1]$$
Se os caracteres forem diferentes ($w_1[i] \neq w_2[j]$), temos três opções de edição. Avaliamos o resultado de aplicar cada uma delas e escolhemos a que produz o menor custo final. Cada operação tem um custo de $1$:
$$dp[i][j] = 1 + \min \begin{cases}
dp[i+1][j] & \text{(Remoção do caractere } w_1[i]\text{)} \\
dp[i+1][j+1] & \text{(Substituição de } w_1[i] \text{ por } w_2[j]\text{)} \\
dp[i][j+1] & \text{(Inserção do caractere } w_2[j] \text{ em } w_1\text{)}
\end{cases}$$
\subsection{Casos Base}
Os casos base ocorrem quando esgotamos os caracteres de uma das strings (ou seja, quando $i$ ou $j$ ultrapassam os limites):
Se consumirmos toda a string $w_1$ ($i \ge N$), mas ainda restarem caracteres em $w_2$, a única forma de igualá-las é \textbf{inserir} todos os $M - j$ caracteres restantes:
$$dp[N][j] = M - j$$
Da mesma forma, se consumirmos toda a string $w_2$ ($j \ge M$), mas ainda restarem caracteres em $w_1$, a única forma de igualá-las é \textbf{remover} todos os $N - i$ caracteres que sobraram:
$$dp[i][M] = N - i$$

View File

@@ -0,0 +1,57 @@
\documentclass[10pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{fullpage}
\usepackage{url}
\pagenumbering{gobble}
\usepackage{hyperref}
\title{ Tutorial: Maior subsequência Crescente II}
\author{}
\date{}
\begin{document}
\maketitle
\section{Solução do Problema}
O problema de encontrar a maior subsequência crescente (\textit{Longest Increasing Subsequence} - LIS) possui uma solução clássica em $O(N^2)$, mas quando $N$ é grande, podemos otimizá-la para \textbf{$O(N \log N)$} combinando \textbf{programação dinâmica com busca binária}. A grande sacada dessa otimização é mudar a perspectiva: em vez de guardar o tamanho da maior subsequência que termina em um índice, guardamos o \textbf{menor valor final} possível para uma subsequência de um determinado comprimento.
\subsection{Definição do Subproblema}
Seja $a$ o nosso vetor de números inteiros de tamanho $N$.
Definimos o nosso estado da programação dinâmica como:
$$d[l] = \text{o menor valor que encerra uma subsequência crescente de comprimento exato } l.$$
O vetor $d$ terá uma propriedade muito importante: ele estará sempre rigorosamente ordenado de forma crescente. A resposta final será o maior $l$ tal que $d[l] \neq \infty$.
\subsection{Função de Transição}
Para calcular $d[l]$, iteramos sobre cada elemento $a[i]$ da nossa sequência original. Como queremos que os elementos finais de cada comprimento sejam os menores possíveis, tentamos usar $a[i]$ para melhorar o nosso vetor $d$.
Como $d$ está ordenado, podemos usar \textbf{busca binária} (\texttt{upper\_bound} ou \texttt{lower\_bound}) para encontrar rapidamente a posição $l$ onde $a[i]$ deve ser inserido. Se $a[i]$ for maior que o final de uma subsequência de tamanho $l-1$ ($d[l-1] < a[i]$) e, ao mesmo tempo, puder substituir um final pior de tamanho $l$ ($a[i] < d[l]$), nós o atualizamos:
$$d[l] = a[i]$$
Isso significa que encontramos uma subsequência crescente de comprimento $l$ que termina em $a[i]$, e esse final é melhor (menor) do que o que tínhamos registrado anteriormente.
\subsection{Casos Base}
Para que a lógica de busca e as comparações iniciais funcionem sem estourar os limites, inicializamos o vetor $d$ de tamanho $N+1$ da seguinte forma:
$$d[0] = -\infty$$
$$d[l] = \infty \quad \text{para todo } 1 \le l \le N$$
Isso representa que uma subsequência de tamanho $0$ termina em um valor infinitamente pequeno, enquanto os outros comprimentos ainda não foram alcançados.
\subsection{Recuperação da Decomposição (Elementos da subsequência)}
Para recuperar os elementos exatos da LIS nesta abordagem otimizada, o vetor $d$ sozinho não basta (pois ele pode conter uma mistura de elementos de diferentes subsequências válidas ao longo do tempo). Precisamos de duas estruturas de rastreamento:
\begin{itemize}
\item $pos[l]$: armazena o \textbf{índice original} do elemento que atualmente ocupa $d[l]$.
\item $p[i]$: armazena o predecessor do elemento de índice $i$, assim como na versão $O(N^2)$.
\end{itemize}
Durante a transição, sempre que atualizamos $d[l] = a[i]$, nós também registramos que esse elemento se encontra no índice $i$ original fazendo $pos[l] = i$.
Ao mesmo tempo, sabemos que o elemento imediatamente anterior a ele na subsequência é o elemento que termina o comprimento $l-1$. Logo, conectamos o predecessor: $p[i] = pos[l-1]$.
Ao final de todas as iterações, sabemos que \texttt{ans} é o comprimento máximo alcançado. O índice do último elemento dessa subsequência estará em $cur = pos[\texttt{ans}]$. A partir desse $cur$, reconstruímos a sequência retrocedendo $cur = p[cur]$ até atingir $-1$, e por fim invertemos a lista para exibi-la na ordem correta.\end{document}

View File

@@ -0,0 +1,44 @@
\section{Solução do Problema}
O problema de encontrar a maior subsequência crescente (\textit{Longest Increasing Subsequence} - LIS) possui uma solução clássica em $O(N^2)$, mas quando $N$ é grande, podemos otimizá-la para \textbf{$O(N \log N)$} combinando \textbf{programação dinâmica com busca binária}. A grande sacada dessa otimização é mudar a perspectiva: em vez de guardar o tamanho da maior subsequência que termina em um índice, guardamos o \textbf{menor valor final} possível para uma subsequência de um determinado comprimento.
\subsection{Definição do Subproblema}
Seja $a$ o nosso vetor de números inteiros de tamanho $N$.
Definimos o nosso estado da programação dinâmica como:
$$d[l] = \text{o menor valor que encerra uma subsequência crescente de comprimento exato } l.$$
O vetor $d$ terá uma propriedade muito importante: ele estará sempre rigorosamente ordenado de forma crescente. A resposta final será o maior $l$ tal que $d[l] \neq \infty$.
\subsection{Função de Transição}
Para calcular $d[l]$, iteramos sobre cada elemento $a[i]$ da nossa sequência original. Como queremos que os elementos finais de cada comprimento sejam os menores possíveis, tentamos usar $a[i]$ para melhorar o nosso vetor $d$.
Como $d$ está ordenado, podemos usar \textbf{busca binária} (\texttt{upper\_bound} ou \texttt{lower\_bound}) para encontrar rapidamente a posição $l$ onde $a[i]$ deve ser inserido. Se $a[i]$ for maior que o final de uma subsequência de tamanho $l-1$ ($d[l-1] < a[i]$) e, ao mesmo tempo, puder substituir um final pior de tamanho $l$ ($a[i] < d[l]$), nós o atualizamos:
$$d[l] = a[i]$$
Isso significa que encontramos uma subsequência crescente de comprimento $l$ que termina em $a[i]$, e esse final é melhor (menor) do que o que tínhamos registrado anteriormente.
\subsection{Casos Base}
Para que a lógica de busca e as comparações iniciais funcionem sem estourar os limites, inicializamos o vetor $d$ de tamanho $N+1$ da seguinte forma:
$$d[0] = -\infty$$
$$d[l] = \infty \quad \text{para todo } 1 \le l \le N$$
Isso representa que uma subsequência de tamanho $0$ termina em um valor infinitamente pequeno, enquanto os outros comprimentos ainda não foram alcançados.
\subsection{Recuperação da Decomposição (Elementos da subsequência)}
Para recuperar os elementos exatos da LIS nesta abordagem otimizada, o vetor $d$ sozinho não basta (pois ele pode conter uma mistura de elementos de diferentes subsequências válidas ao longo do tempo). Precisamos de duas estruturas de rastreamento:
\begin{itemize}
\item $pos[l]$: armazena o \textbf{índice original} do elemento que atualmente ocupa $d[l]$.
\item $p[i]$: armazena o predecessor do elemento de índice $i$, assim como na versão $O(N^2)$.
\end{itemize}
Durante a transição, sempre que atualizamos $d[l] = a[i]$, nós também registramos que esse elemento se encontra no índice $i$ original fazendo $pos[l] = i$.
Ao mesmo tempo, sabemos que o elemento imediatamente anterior a ele na subsequência é o elemento que termina o comprimento $l-1$. Logo, conectamos o predecessor: $p[i] = pos[l-1]$.
Ao final de todas as iterações, sabemos que \texttt{ans} é o comprimento máximo alcançado. O índice do último elemento dessa subsequência estará em $cur = pos[\texttt{ans}]$. A partir desse $cur$, reconstruímos a sequência retrocedendo $cur = p[cur]$ até atingir $-1$, e por fim invertemos a lista para exibi-la na ordem correta.

View File

@@ -0,0 +1,48 @@
\documentclass[10pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{fullpage}
\usepackage{url}
\pagenumbering{gobble}
\usepackage{hyperref}
\title{ Tutorial: Maior subsequência Crescente}
\author{}
\date{}
\begin{document}
\maketitle
\section{Solução do Problema}
O problema de encontrar a maior subsequência crescente (\textit{Longest Increasing Subsequence} - LIS) é um clássico que pode ser resolvido de forma eficiente por meio de \textbf{programação dinâmica}. A ideia central é focar no final da subsequência: para cada elemento da sequência original, calculamos qual é a maior subsequência crescente que termina exatamente nele.
\subsection{Definição do Subproblema}
Seja $a$ o nosso vetor de números inteiros com $N$ elementos, indexado de $0$ a $N-1$.
Definimos o nosso estado da programação dinâmica como:
$$d[i] = \text{o comprimento da maior subsequência crescente que termina obrigatoriamente no índice } i.$$
A resposta final para o tamanho da maior subsequência não será necessariamente $d[N-1]$, mas sim o maior valor alcançado em todo o vetor $d$, ou seja, $\max(d[i])$ para $0 \le i < N$.
\subsection{Função de Transição}
Para determinar o valor de $d[i]$, precisamos olhar para todos os elementos anteriores à posição $i$, ou seja, um índice $j$ tal que $0 \le j < i$.
Se encontrarmos um elemento anterior que seja estritamente menor que o elemento atual ($a[j] < a[i]$), isso significa que podemos estender a subsequência que terminava em $j$ anexando o elemento $a[i]$ ao final dela. O novo comprimento seria $d[j] + 1$.
Avaliamos todos os possíveis índices $j$ válidos e mantemos o que gera o maior comprimento:
$$d[i] = \max_{\substack{0 \le j < i \\ a[j] < a[i]}} \big( d[j] + 1 \big)$$
\subsection{Casos Base}
O caso base é bastante intuitivo: na pior das hipóteses, qualquer elemento isolado forma, por si só, uma subsequência válida de comprimento $1$. Portanto, a nossa inicialização padrão para todos os estados é:
$$d[i] = 1 \quad \text{para todo } 0 \le i < N$$
\subsection{Recuperação da Decomposição (Elementos da subsequência)}
Como o problema exige a impressão dos elementos que formam a subsequência, precisamos reconstruir o caminho ótimo. Para isso, utilizamos um vetor auxiliar $p[i]$ (de \textit{parent} ou predecessor), inicializado com $-1$.
Durante a transição, sempre que atualizamos $d[i]$ com um valor maior proveniente de um $d[j] + 1$, registramos o índice de origem definindo $p[i] = j$.
Após calcularmos todo o vetor $d$, localizamos o índice \texttt{pos} que contém o valor máximo absoluto de $d$. A partir de \texttt{pos}, recuperamos os elementos da subsequência percorrendo o caminho reverso: adicionamos $a[\texttt{pos}]$ à nossa lista de resposta e saltamos para o predecessor atualizando $\texttt{pos} = p[\texttt{pos}]$. Repetimos o processo até que $\texttt{pos}$ seja $-1$. Como resgatamos os elementos do final para o começo, basta inverter a lista resultante para apresentar a subsequência na ordem crescente original.\end{document}

View File

@@ -0,0 +1,35 @@
\section{Solução do Problema}
O problema de encontrar a maior subsequência crescente (\textit{Longest Increasing Subsequence} - LIS) é um clássico que pode ser resolvido de forma eficiente por meio de \textbf{programação dinâmica}. A ideia central é focar no final da subsequência: para cada elemento da sequência original, calculamos qual é a maior subsequência crescente que termina exatamente nele.
\subsection{Definição do Subproblema}
Seja $a$ o nosso vetor de números inteiros com $N$ elementos, indexado de $0$ a $N-1$.
Definimos o nosso estado da programação dinâmica como:
$$d[i] = \text{o comprimento da maior subsequência crescente que termina obrigatoriamente no índice } i.$$
A resposta final para o tamanho da maior subsequência não será necessariamente $d[N-1]$, mas sim o maior valor alcançado em todo o vetor $d$, ou seja, $\max(d[i])$ para $0 \le i < N$.
\subsection{Função de Transição}
Para determinar o valor de $d[i]$, precisamos olhar para todos os elementos anteriores à posição $i$, ou seja, um índice $j$ tal que $0 \le j < i$.
Se encontrarmos um elemento anterior que seja estritamente menor que o elemento atual ($a[j] < a[i]$), isso significa que podemos estender a subsequência que terminava em $j$ anexando o elemento $a[i]$ ao final dela. O novo comprimento seria $d[j] + 1$.
Avaliamos todos os possíveis índices $j$ válidos e mantemos o que gera o maior comprimento:
$$d[i] = \max_{\substack{0 \le j < i \\ a[j] < a[i]}} \big( d[j] + 1 \big)$$
\subsection{Casos Base}
O caso base é bastante intuitivo: na pior das hipóteses, qualquer elemento isolado forma, por si só, uma subsequência válida de comprimento $1$. Portanto, a nossa inicialização padrão para todos os estados é:
$$d[i] = 1 \quad \text{para todo } 0 \le i < N$$
\subsection{Recuperação da Decomposição (Elementos da subsequência)}
Como o problema exige a impressão dos elementos que formam a subsequência, precisamos reconstruir o caminho ótimo. Para isso, utilizamos um vetor auxiliar $p[i]$ (de \textit{parent} ou predecessor), inicializado com $-1$.
Durante a transição, sempre que atualizamos $d[i]$ com um valor maior proveniente de um $d[j] + 1$, registramos o índice de origem definindo $p[i] = j$.
Após calcularmos todo o vetor $d$, localizamos o índice \texttt{pos} que contém o valor máximo absoluto de $d$. A partir de \texttt{pos}, recuperamos os elementos da subsequência percorrendo o caminho reverso: adicionamos $a[\texttt{pos}]$ à nossa lista de resposta e saltamos para o predecessor atualizando $\texttt{pos} = p[\texttt{pos}]$. Repetimos o processo até que $\texttt{pos}$ seja $-1$. Como resgatamos os elementos do final para o começo, basta inverter a lista resultante para apresentar a subsequência na ordem crescente original.

View File

@@ -0,0 +1,47 @@
\documentclass[10pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{fullpage}
\usepackage{url}
\pagenumbering{gobble}
\usepackage{hyperref}
\title{ Tutorial: Maior Subsequência Palindrômica}
\author{}
\date{}
\begin{document}
\maketitle
\section{Solução do Problema}
O problema de encontrar a Maior Subsequência Palindrômica (\textit{Longest Palindromic Subsequence} - LPS) pode ser resolvido de forma elegante utilizando \textbf{programação dinâmica em intervalos}. A ideia é analisar o problema de fora para dentro: comparamos os caracteres nas extremidades de uma substring e decidimos se eles farão parte do nosso palíndromo ou se devemos encolher o nosso intervalo de busca.
\subsection{Definição do Subproblema}
Seja $S$ a nossa string original de tamanho $N$, indexada de $0$ a $N-1$.
Definimos o nosso estado da programação dinâmica como:
$$dp[i][j] = \text{o comprimento da maior subsequência palindrômica dentro da substring } S[i \dots j].$$
Assim, a resposta para o comprimento máximo em toda a string será encontrada em $dp[0][N-1]$, que engloba o intervalo completo do primeiro ao último caractere.
\subsection{Função de Transição}
Para calcular $dp[i][j]$, olhamos para os caracteres nas pontas do intervalo atual: $S[i]$ e $S[j]$. Temos dois cenários possíveis:
1. \textbf{Os caracteres são iguais ($S[i] == S[j]$):} Eles podem formar as pontas de um palíndromo. Nesse caso, ganhamos $2$ de comprimento (um caractere de cada lado) e somamos ao melhor palíndromo que conseguimos formar no intervalo estritamente interno a eles.
$$dp[i][j] = dp[i+1][j-1] + 2$$
2. \textbf{Os caracteres são diferentes ($S[i] \neq S[j]$):} Como eles são diferentes, não podem formar as pontas do mesmo palíndromo simultaneamente. Precisamos descartar um dos lados e ver qual escolha nos dá o melhor resultado. Calculamos ignorando o caractere da esquerda (reduzindo para $i+1$) ou ignorando o da direita (reduzindo para $j-1$).
$$dp[i][j] = \max \big( dp[i+1][j], dp[i][j-1] \big)$$
\textit{Nota de implementação:} Para garantir que $dp[i+1][j-1]$ ou as outras dependências já estejam calculadas quando precisarmos delas, os laços devem processar os intervalos do menor para o maior, ou preenchendo o $i$ de trás para frente (de $N-1$ até $0$) e o $j$ de frente para trás (de $i+1$ até $N-1$).
\subsection{Casos Base}
Os casos base ocorrem para intervalos de tamanho 1 e intervalos inválidos (tamanho 0 ou negativo):
Todo caractere isolado é, por definição, um palíndromo de comprimento 1. Portanto, a diagonal principal da nossa matriz de estados recebe $1$:
$$dp[i][i] = 1 \quad \text{para todo } 0 \le i < N$$
Para intervalos onde o início ultrapassa o fim (o que pode ocorrer durante a transição de $dp[i+1][j-1]$ quando $j = i+1$), o comprimento é zero:
$$dp[i][j] = 0 \quad \text{para todo } i > j$$\end{document}

View File

@@ -0,0 +1,34 @@
\section{Solução do Problema}
O problema de encontrar a Maior Subsequência Palindrômica (\textit{Longest Palindromic Subsequence} - LPS) pode ser resolvido de forma elegante utilizando \textbf{programação dinâmica em intervalos}. A ideia é analisar o problema de fora para dentro: comparamos os caracteres nas extremidades de uma substring e decidimos se eles farão parte do nosso palíndromo ou se devemos encolher o nosso intervalo de busca.
\subsection{Definição do Subproblema}
Seja $S$ a nossa string original de tamanho $N$, indexada de $0$ a $N-1$.
Definimos o nosso estado da programação dinâmica como:
$$dp[i][j] = \text{o comprimento da maior subsequência palindrômica dentro da substring } S[i \dots j].$$
Assim, a resposta para o comprimento máximo em toda a string será encontrada em $dp[0][N-1]$, que engloba o intervalo completo do primeiro ao último caractere.
\subsection{Função de Transição}
Para calcular $dp[i][j]$, olhamos para os caracteres nas pontas do intervalo atual: $S[i]$ e $S[j]$. Temos dois cenários possíveis:
1. \textbf{Os caracteres são iguais ($S[i] == S[j]$):} Eles podem formar as pontas de um palíndromo. Nesse caso, ganhamos $2$ de comprimento (um caractere de cada lado) e somamos ao melhor palíndromo que conseguimos formar no intervalo estritamente interno a eles.
$$dp[i][j] = dp[i+1][j-1] + 2$$
2. \textbf{Os caracteres são diferentes ($S[i] \neq S[j]$):} Como eles são diferentes, não podem formar as pontas do mesmo palíndromo simultaneamente. Precisamos descartar um dos lados e ver qual escolha nos dá o melhor resultado. Calculamos ignorando o caractere da esquerda (reduzindo para $i+1$) ou ignorando o da direita (reduzindo para $j-1$).
$$dp[i][j] = \max \big( dp[i+1][j], dp[i][j-1] \big)$$
\textit{Nota de implementação:} Para garantir que $dp[i+1][j-1]$ ou as outras dependências já estejam calculadas quando precisarmos delas, os laços devem processar os intervalos do menor para o maior, ou preenchendo o $i$ de trás para frente (de $N-1$ até $0$) e o $j$ de frente para trás (de $i+1$ até $N-1$).
\subsection{Casos Base}
Os casos base ocorrem para intervalos de tamanho 1 e intervalos inválidos (tamanho 0 ou negativo):
Todo caractere isolado é, por definição, um palíndromo de comprimento 1. Portanto, a diagonal principal da nossa matriz de estados recebe $1$:
$$dp[i][i] = 1 \quad \text{para todo } 0 \le i < N$$
Para intervalos onde o início ultrapassa o fim (o que pode ocorrer durante a transição de $dp[i+1][j-1]$ quando $j = i+1$), o comprimento é zero:
$$dp[i][j] = 0 \quad \text{para todo } i > j$$