Directorio de artículos
1. Pasos para resolver problemas de programación dinámica.
La idea general para resolver problemas de Programación Dinámica (DP) se puede dividir en los siguientes pasos:
- Defina el estado : primero, debe definir claramente el estado del problema. El estado es información clave que describe el problema y generalmente incluye las variables que deben optimizarse. La definición del estado debe reflejar las características del problema y puede calcularse de forma recursiva o iterativa.
- Definir la ecuación de transición de estados : una vez definido el estado, el siguiente paso es establecer la relación entre estados, que es la ecuación de transición de estados. Las ecuaciones de transición de estado describen cómo calcular un nuevo estado a partir de un estado conocido. Este es el núcleo del problema de la programación dinámica.
- Inicialización : determina el valor del estado inicial, generalmente las condiciones de contorno o el caso base del problema. Estos estados iniciales son el punto de partida del algoritmo de programación dinámica.
- Cálculo recursivo o iterativo : utilice ecuaciones de transición de estado para calcular gradualmente la solución óptima al problema a partir del estado inicial. Esto se puede lograr mediante un enfoque iterativo ascendente o un enfoque recursivo de arriba hacia abajo.
- Registrar ruta (opcional): si necesita restaurar la ruta específica de la solución óptima, puede registrar la fuente de cada estado durante el proceso de transferencia de estado para facilitar la restauración posterior de la ruta.
- Resultados devueltos : De acuerdo con los requisitos del problema, obtenga la solución óptima requerida o el valor óptimo del estado final.
- Optimización de la complejidad del espacio (opcional): en algunos casos, la complejidad del espacio de los algoritmos de programación dinámica se puede optimizar, como retener solo los estados intermedios necesarios en lugar de registrarlos todos.
2. Centrarse en ejemplos clásicos
5. La subcadena palíndromo más larga.
Dada una cadena s, encuentre la subcadena palíndromo más larga en s.
Si el orden inverso de una cuerda es el mismo que el de la cuerda original, la cuerda se llama cuerda palíndromo.
Ejemplo 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
Ejemplo 2:
输入:s = "cbbd"
输出:"bb"
- Ideas
- Definir estado: defina el estado
dp[i][j]
para indicars[i...j]
si la cadena es una subcadena palíndromo - Definir la ecuación de transición de estado
- Si
s[i]
nos[j]
es el mismo que el carácter, obviamente no ess[i...j]
una cadena palíndromo. - si es igual
s[i]
als[j]
personaje -
- Si
i
yj
son subíndices adyacentes, es decir,j-i==1
entonces ,s[i...j]
obviamente es una cadena palíndromo.
- Si
-
- Si
i
yj
no son subíndices adyacentes, sis[i...j]
es un palíndromo depende des[i+1...j-1]
si es un palíndromo.
- Si
Entonces la ecuación de transición de estado:
if (s[i] == s[j]) {
dp[i][j] = j - i == 1 ? true : dp[i + 1][j - 1];
} else {
dp[i][j] = false;
}
- Inicialización: cada carácter es una cadena palíndromo, por lo que
dp[i][i]
se inicializa entrue
(0<=i<n), y otras inicializaciones sonfalse
- Cálculo recursivo o iterativo: como
dp[i][j]
dependedp[i+1][j-1]
, el estado se transfiere de una cadena más corta a una cadena más larga - Resultado de retorno: debido a que necesitamos encontrar la cadena de palíndromo más larga, definimos una
start
variable para representar el subíndice inicial de la subcadena de palíndromo más larga y unamaxLen
variable para representar la longitud de la subcadena de palíndromo más larga, y luego cada vez que encontramos una subcadena de palíndromo , determine si la longitud de la cadena es mayor que la longitud de la subcadena palíndromo más larga actual y, si es más larga, actualice la información de las dos variables marcadas con la subcadena palíndromo más larga. El retorno finals.substr(start, maxLen)
es la subcadena palíndromo más larga.
- Código C++
class Solution {
public:
string longestPalindrome(string s) {
if (s.size() <= 1) {
return s;
}
int n = s.size();
// 定义状态:dp[i][j]表示s[i...j]是否为回文串
vector<vector<bool>> dp(n, vector<bool>(n, false));
// 初始化
for (int i = 0; i < n; ++i) {
dp[i][i] = true;
}
int start = 0; // 记录最长回文子串的起始下标
int maxLen = 1; // 记录最长回文子串的长度(最少一个字符本身就是回文串)
for (int len = 2; len <= n; ++len) {
// 注意需要从长度较短的字符串向长度较长的字符串进行转移
for (int i = 0; i < n; ++i) {
int j = len + i - 1;
if (j >= n) {
break;
}
// 状态转移方程
if (s[i] == s[j]) {
dp[i][j] = j - i == 1 ? true : dp[i + 1][j - 1];
} else {
dp[i][j] = false;
}
// 更新最长回文子串信息
if (dp[i][j] && j - i + 1 > maxLen) {
start = i;
maxLen = j - i + 1;
}
}
}
return s.substr(start, maxLen);
}
};
32. Soporte válido más largo
Dada una cadena que contiene solo '(' y ')', encuentre la longitud de la subcadena de corchetes válida más larga (bien formada y consecutiva).
Ejemplo 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
Ejemplo 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
Ejemplo 3:
输入:s = ""
输出:0
pista:
0 <= s.length <= 3 * 104
s[i] 为 '(' 或 ')'
- Ideas
- Definir estado: utilizar
dp[i]
representantesi
La longitud de corchete válida más larga que termina en carácter(Empiezo de 0 a n-1), por ejemplo(()
, haydp[0]=0,dp[1]=0,dp[2]=2
; - Defina la ecuación de transición de estado:
- Si
s[i]
es un corchete izquierdo(
, obviamentedp[i]=0
, porque el corchete izquierdo no puede ser el carácter final de una cadena de corchetes válidos [como una cadena((
] - Si
s[i]
es un corchete derecho)
, es necesario discutirlo caso por caso dependiendo de si el carácter anterior es un corchete izquierdo o un corchete derecho. -
- Si
s[i-1]
es un corchete izquierdo(
, entoncesdp[i]=dp[i-2]+2
(i>=2), [como una cadena(())()
,dp[5]=dp[3]+2=4+2=6
]
- Si
-
- Si
s[i-1]
es un corchete derecho)
, primero busquei-1
el carácter antes del corchete válido más largo que termine con el carácter enésimo, es decirs[i-dp[i-1]-1]
,
- Si
-
-
- Si este carácter es un corchete izquierdo
(
, entoncesdp[i]=dp[i-1]+2+dp[i-dp[i-1]-2]
, [por ejemplo, cadena()(())
,dp[5]=dp[4]+2+dp[1]=2+2+2=6
]
- Si este carácter es un corchete izquierdo
-
-
-
- Si este carácter es un corchete derecho
)
, entoncesdp[i]=0
(no puede formar una cadena de corchetes válida) [por ejemplo,)())
es una cadena no válida]
- Si este carácter es un corchete derecho
-
La ecuación de transición de estado se abrevia como:
if (s[i] == ')') {
if (s[i - 1] == '(') {
dp[i] = i >= 2 ? dp[i - 2] + 2 : 2;
} else if (s[i - 1] == ')' && i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] == '(') {
dp[i] = i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] + dp[i - 1] + 2 : dp[i - 1] + 2;
}
}
- inicialización:
dp[i]=0
- Cálculo recursivo o iterativo: comience a recorrer desde
i
igual a , debido a que al menos dos caracteres pueden formar un paréntesis válido, debe ser1
dp[0]
0
- Resultados de devolución: cada
dp[i]
proceso de cálculo se actualiza al mismo tiempomaxLen
- Código C++
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.size();
vector<int> dp(n, 0);
int maxLen = 0;
for (int i = 1; i < n; ++i) {
if (s[i] == '(') {
continue;
}
if (s[i - 1] == '(') {
dp[i] = i >= 2 ? dp[i - 2] + 2 : 2;
} else if (s[i - 1] == ')' && i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] == '(') {
dp[i] = i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] + dp[i - 1] + 2 : dp[i - 1] + 2;
}
maxLen = max(maxLen, dp[i]);
}
return maxLen;
}
};
53. Suma máxima de subarreglo
Dada una matriz de números enteros, busque una submatriz continua con la suma máxima (la submatriz contiene al menos un elemento) y devuelva su suma máxima.
Un subarreglo es una porción contigua de un arreglo.
Ejemplo 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
Ejemplo 2:
输入:nums = [1]
输出:1
Ejemplo 3:
输入:nums = [5,4,-1,7,8]
输出:23
pista:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
- Ideas
- Definir estado:
dp[i]
representado porSuma máxima de una matriz que termina con el número i-ésimo - Defina la ecuación de transición de estado:
Si la suma máxima del enésimodp[i] = max(dp[i - 1] + nums[i], nums[i]); (i>=1)
i
número más la matriz que termina en el enésimo número es menor que el enésimo número en sí, es mejor no sumarlo.i-1
i
- inicialización:
dp[0]=nums[0]
- Cálculo recursivo o iterativo:
i=1
recorrer de principio a finn-1
- Resultado de retorno: establezca un
maxSum
valor inicial de y el tamaño senums[0]
actualizará en cada ronda de recorrido posterior.maxSum
- Código C++
-
- versión de matriz dp:
class Solution { public: int maxSubArray(vector<int>& nums) { int n = nums.size(); // dp[i]表示以第i个数结尾的数组的最大连续子数组的和 vector<int> dp(n, 0); // 初始化 dp[0] = nums[0]; int maxSum = nums[0]; for (int i = 1; i < n; ++i) { dp[i] = max(dp[i - 1] + nums[i], nums[i]); maxSum = max(maxSum, dp[i]); } return maxSum; }
- versión de matriz dp:
-
- Versión simplificada:
dp[i]
solodp[i-1]
se utilizan los datos, por lo que puedes usar directamente una variablesum
para reemplazarlos.class Solution { public: int maxSubArray(vector<int>& nums) { int n = nums.size(); int sum = nums[0]; int maxSum = sum; for (int i = 1; i < n; ++i) { sum = max(sum + nums[i], nums[i]); maxSum = max(maxSum, sum); } return maxSum; } };
- Versión simplificada: