[LeetCode] -Programación dinámica-3

prefacio

Registre las preguntas relacionadas con la programación dinámica encontradas al resolver preguntas en LeetCode, Parte 3

322. Cambio de cambio

dp[i] representa la cantidad mínima de monedas necesarias para generar i yuanes. La denominación de cada moneda es monedas[j], entonces dp[i] es el número mínimo de monedas necesarias para hacer i - monedas[j] más 1. El "1" añadido significa que la denominación es monedas[La moneda de j]

public int coinChange(int[] coins, int amount) {
    
    
    int[] dp = new int[amount + 1];
    //后续需要Math.min来选较小数,所以dp数组每个元素先初始化为一个较大值。
    //注意到下面有“dp[i - coins[j]] + 1”,
    //所以为了防止溢出,不能直接使用Integer.MAX_VALUE,需要小一点,这里只减一即可
    Arrays.fill(dp, Integer.MAX_VALUE - 1);
    dp[0] = 0; //边界,凑0块钱需要0个硬币
    for (int i = 1; i <= amount; i++) {
    
    
        for (int j = 0; j < coins.length; j++) {
    
    
            if (coins[j] <= i) {
    
     //coins[j]小于等于i才能尝试用coins[j]来凑出i块钱
                dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
            }
        }
    }
    //如果dp[amount]没有改变值说明无法凑出amount,返回-1
    return dp[amount] == Integer.MAX_VALUE - 1 ? -1 : dp[amount];
}

518. Cambio de Intercambio II

dp[i] representa el número de métodos de intercambio para el monto i. Para una cantidad i, enumere cada moneda cuyo valor nominal sea menor o igual, luego puedo intercambiar esta moneda con i menos la cantidad restante de esta moneda, entonces dp[i] += dp[i - moneda]. Basado en esta idea, se puede escribir el siguiente código

public int change(int amount, int[] coins) {
    
    
    int[] dp = new int[amount + 1];
    dp[0] = 1;
    for(int i = 1;i <= amount;i++){
    
    
        for(int j = 0;j < coins.length;j++){
    
    
            if(i - coins[j] >= 0) dp[i] += dp[i - coins[j]];
        }
    }
    return dp[amount];
}

Este código es incorrecto. Por ejemplo: cantidad = 5, monedas = [1, 2, 5], la cantidad 1 tiene un método de cambio 1; la cantidad 2 tiene dos métodos de cambio 1 + 1, 2; luego, cuando se calcule la cantidad 3, siga el algoritmo anterior. Obtendrá 3 métodos de canje, 1 + 1 + 1, 1 + 2, 2 + 1. Se puede encontrar que existen métodos de intercambio repetidos, es decir, algunas monedas se han utilizado varias veces utilizando diferentes métodos de intercambio.
Entonces, ¿cómo se hace para que una moneda sólo se use una vez? Simplemente intercambie el orden de los dos niveles de bucles for y repita primero las monedas y luego las cantidades:

public int change(int amount, int[] coins) {
    
    
    int[] dp = new int[amount + 1];
    dp[0] = 1;
    for(int coin : coins){
    
    
        for(int i = coin;i <= amount;i++){
    
    
            dp[i] += dp[i - coin];
        }
    }
    return dp[amount];
}

97. Cuerdas entrelazadas

Consulte la solución oficial.

Definición de estado : dp[i][j] indica si los primeros i caracteres en s1 y los primeros j caracteres en s2 (tenga en cuenta que el subíndice indica el número de caracteres en lugar del índice de los caracteres en la cadena original) pueden formar el primeros caracteres i en s3 caracteres i + j

Transición de estado : si los primeros caracteres i - 1 en s1 y los primeros j caracteres en s2 pueden formar los primeros caracteres i + j - 1 en s3, entonces si el carácter i - 1 (comenzando desde 0 según el subíndice) en s1 Es lo mismo que el carácter i + j - 1 en s3, lo que significa que los primeros caracteres i en s1 y los primeros caracteres j en s2 pueden formar los primeros caracteres i + j en s3, es decir, dp [i] j] | = dp[i - 1][j] && c1[i - 1] == c3[i + j - 1]

De la misma manera, dp[i][j] |= dp[i][j - 1] && c2[j - 1] == c3[i + j - 1]

Límite : dp [0] [0] = verdadero, lo que indica que dos cadenas vacías se pueden intercalar para formar una cadena vacía

public boolean isInterleave(String s1, String s2, String s3) {
    
    
    char[] c1 = s1.toCharArray();
    char[] c2 = s2.toCharArray();
    char[] c3 = s3.toCharArray();
    int l1 = c1.length;
    int l2 = c2.length;
    //s1跟s2的长度和不等于s3的长度的话肯定就不能交错形成s3
    if(l1 + l2 != c3.length) return false;
    /*
    boolean[][] dp = new boolean[l1 + 1][l2 + 1];
    dp[0][0] = true;
    for(int i = 0;i <= l1;i++){
        for(int j = 0;j <= l2;j++){
        	//根据状态转移方程,可以使用状态压缩
            if(i > 0){
            	//这里的dp[i][j]一定是false,所以没必要使用|=
                dp[i][j] = dp[i - 1][j] && c1[i - 1] == c3[i + j - 1];
            }
            //原本dp[i][j]都是false(除了(0,0)),在判断判断j>0之前先判断了i>0,所以在
            //此处dp[i][j]可能已经被修改了,所以要使用|=
            if(j > 0){
                dp[i][j] |= dp[i][j - 1] && c2[j - 1] == c3[i + j - 1];
            }
        }
    }
    return dp[l1][l2];
    */
    boolean[] dp = new boolean[l2 + 1];
    dp[0] = true;
    for(int i = 0;i <= l1;i++){
    
    
        for(int j = 0;j <= l2;j++){
    
    
            if(i > 0){
    
    
                dp[j] = dp[j] && c1[i - 1] == c3[i + j - 1];
            }
            if(j > 0){
    
    
                dp[j] |= dp[j - 1] && c2[j - 1] == c3[i + j - 1];
            }
        }
    }
    return dp[l2];
}

221. Plaza más grande

Estado : dp[i][j] representa el valor máximo de la longitud del lado de un cuadrado que contiene solo 1 con la matriz [i][j] como esquina inferior derecha

Transición de estado : si la matriz [i] [j] es 0, entonces no puede haber un cuadrado que contenga solo 1 con la matriz [i] [j] como la esquina inferior derecha, y dp [i] [j] es 0; si la matriz [i] ][j] es 1, la ecuación de transición de estado debe ser dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1 ]), dp [i - 1][j - 1]) + 1, es decir, la longitud mínima del lado de un cuadrado que contiene solo 1 con la cuadrícula izquierda como esquina inferior derecha, la cuadrícula superior como esquina inferior derecha, y la cuadrícula superior izquierda como la esquina inferior derecha. El valor más pequeño entre los valores. ¿Por qué elegiste esto?

Suponiendo que actualmente se deduzca dp[3][3], si el valor máximo de la longitud del lado de un cuadrado que contiene solo 1 es 2 con la matriz de cuadrícula[3][2] a la izquierda como esquina inferior derecha, Si desea que dp[i] [j] sea 3, requiere que los valores de matriz[1][1], matriz[1][2], matriz[1][3] y matriz[ 2] [3] son ​​todos 1, es decir, dp [2] [2], dp [2] [3] debe ser mayor o igual a 2. De la misma manera, podemos analizar la situación donde la cuadrícula superior y la cuadrícula superior izquierda son la esquina inferior derecha. Entonces finalmente podemos encontrar que la longitud máxima del lado de un cuadrado que contiene solo 1 con la matriz [i] [j] como esquina inferior derecha debe ser dp [i - 1] [j], dp [i] [j - 1] , dp[i - 1][j - 1] El valor mínimo entre los tres más uno

Límite : el cuadrado con la cuadrícula en la primera fila y la primera columna como esquina inferior derecha solo puede tener una longitud lateral máxima de 1, por lo que dp[i][j] = 1 (i == 0 || j == 0)

public int maximalSquare(char[][] matrix) {
    
    
    int maxSide = 0;
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
    
    
        return maxSide;
    }
    int rows = matrix.length, columns = matrix[0].length;
    int[][] dp = new int[rows][columns];
    for (int i = 0; i < rows; i++) {
    
    
        for (int j = 0; j < columns; j++) {
    
    
            if (matrix[i][j] == '1') {
    
    
                if (i == 0 || j == 0) {
    
    
                	//边界上的点的最大边长只能为1
                    dp[i][j] = 1;
                } else {
    
    
                    dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                }
                maxSide = Math.max(maxSide, dp[i][j]);
            }
        }
    }
    return maxSide * maxSide;
}

983. Tarifa más baja

Un breve resumen basado en la solución al problema :
En primer lugar, si compras un billete para n días el mismo día, debes comprar un billete n días después para no desperdiciar dinero.
Entonces, si el estado dp[i] se usa para denotar el costo mínimo requerido para derivar el i-ésimo día, entonces, para el i-ésimo día, hay tres decisiones: comprar un pase de un día/comprar un pase de siete días /comprar un pase de 30 días, dp[i] debe ser igual al valor mínimo del precio del boleto obtenido por estas tres decisiones.
Si compras un pase de un día, dp[i] es igual al costo[0] más el costo mínimo dp[i + 1] después de 1 día, es decir, dp [ i + 1 ] + costos [ 0 ] dp[i + 1] + costos [0]dp [ yo _+1 ]+cos t s [ 0 ]
De manera similar, si se utiliza la decisión 2,dp [ i ] = dp [ i + 7 ] + costo [ 1 ] dp[i] = dp[i + 7] + costo[1]dp [ yo ] _=dp [ yo _+7 ]+costo t [ 1 ]
Si se usa la decisión 3,dp [ i ] = dp [ i + 30 ] + costo [ 2 ] dp[i] = dp[i + 30] + costo[2]dp [ yo ] _=dp [ yo _+30 ]+cos t [ 2 ]
por lo que debe derivarse de atrás hacia adelante

En el proceso de derivación real, i comienza a disminuir desde el número máximo de días en días hasta el número mínimo de días. Cuando se encuentra un número de días que no está en días, dp [i] es directamente igual al costo del siguiente día dp[i + 1]

public int mincostTickets(int[] days, int[] costs) {
    
    
    int len = days.length, maxDay = days[len - 1], minDay = days[0];
    //在后续推导时需要对 cur+30,可以先判断cur的范围,也可以像下面这样直接把数组调大
    int[] dp = new int[maxDay + 31];
    int index = len - 1;
    for (int cur = maxDay;cur >= minDay;cur--) {
    
    
        if (cur == days[index]) {
    
    
            dp[cur] = Math.min(dp[cur + 1] + costs[0], dp[cur + 7] + costs[1]);
            dp[cur] = Math.min(dp[cur], dp[cur + 30] + costs[2]);
            index--; 
        } else {
    
    
            dp[cur] = dp[cur + 1];
        }
    }
    return dp[minDay]; 
}

1143. Subsecuencia común más larga

Para problemas de programación dinámica de tipo matriz o cadena como este, si solo hay una matriz o cadena, entonces puede usar la matriz unidimensional dp, donde dp[i] representa el intervalo [0,i]; si hay dos matrices o cadenas, luego use una matriz bidimensional, dp[i][j] representa el intervalo [0,i] en la matriz 1/cadena 1 y el intervalo [0,j] en la matriz 2/cadena 2, y luego considerar si la reducción de dimensionalidad

En esta pregunta, el estado dp[i][j] representa la longitud de la subsecuencia común más larga entre las primeras i letras del texto1 y las primeras j letras del texto2, entonces la ecuación de transición de estado es dp[i][j] =
dp [i - 1][j - 1] + 1, texto1[i - 1] == texto2[j - 1] dp[
i][j] = max(dp[i][j - 1],dp[ i - 1][j]), otras situaciones

La respuesta final es dp[len1][len2]

public int longestCommonSubsequence(String text1, String text2) {
    
    
    char[] cs1 = text1.toCharArray();
    char[] cs2 = text2.toCharArray();
    int len1 = cs1.length;
    int len2 = cs2.length;
    int[][] dp = new int[len1 + 1][len2 + 1];
    for(int i = 1;i <= len1;i++){
    
    
        for(int j = 1;j <= len2;j++){
    
    
            if(cs1[i - 1] == cs2[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[len1][len2];
}

64. Suma de ruta mínima

Usando programación dinámica, dp[i][j] en la matriz dp bidimensional representa la ruta mínima a grid[i][j] y

Si desea llegar a un punto de la cuadrícula[i][j], solo puede llegar a él dando un paso hacia abajo desde la cuadrícula[i - 1][j] o dando un paso a la derecha desde la cuadrícula[i][j - 1]. Entonces, la suma mínima de la ruta a grid[i][j] es naturalmente min(dp[i - 1][j],dp[i][j - 1]) + grid[i][j]. Como en el caso anterior No nos importa cuál sea la ruta, éste es el poder de la programación dinámica.

public int minPathSum(int[][] grid) {
    
    
    int m = grid.length,n = grid[0].length;
    int[][] dp = new int[m][n];
    dp[0][0] = grid[0][0];
    //对于第一列的点只能从上一个点向下走一步到达
    for(int i = 1;i < m;i++){
    
    
        dp[i][0] = dp[i - 1][0] + grid[i][0];
    }
    //对于第一行的点只能从左边的点向右走一步到达
    for(int i = 1;i < n;i++){
    
    
        dp[0][i] = dp[0][i - 1] + grid[0][i];
    }
    //开始状态转移
    for(int i = 1;i < m;i++){
    
    
        for(int j = 1;j < n;j++){
    
    
            dp[i][j] = Math.min(dp[i - 1][j],dp[i][j - 1]) + grid[i][j];
        }
    }
    //最后返回走到右下角即grid[m - 1][n - 1]的最小路径和
    return dp[m - 1][n - 1];
}

Reducción de dimensionalidad

La antigua regla, dp bidimensional, se puede reducir a dp unidimensional según la ecuación de transición de estado.

public int minPathSum(int[][] grid) {
    
    
    int m = grid.length,n = grid[0].length;
    int[] dp = new int[n];
    dp[0] = grid[0][0];
    for(int i = 1;i < n;i++){
    
    
        dp[i] = dp[i - 1] + grid[0][i];
    }
    for(int i = 1;i < m;i++){
    
    
        for(int j = 0;j < n;j++){
    
    
            if(j == 0) dp[j] = dp[j] + grid[i][j];
            else dp[j] = Math.min(dp[j],dp[j - 1]) + grid[i][j];
        }
    }
    return dp[n - 1];
}

152. Subconjunto máximo de producto

El estado dp[i][0] representa el producto máximo que se puede obtener del subarreglo con nums[i] como último elemento, y dp[i][1] representa el producto mínimo que se puede obtener. Siempre hay dp[i][0] >= dp[i][1],
el límite es el primer elemento, dp[0][0] = dp[0][1] = nums[0]
para nums[ yo]:

  1. Si nums[i] > 0: entonces si dp[i - 1][0] también es mayor que 0, entonces dp[i][0] será igual a dp[i - 1][0] * nums[i ]; de lo contrario, dp [i - 1][0] es menor o igual a 0, entonces dp[i - 1][1] es menor y el producto máximo que se puede acumular en nums[i] solo puede ser nums [i]. Si dp[i - 1][1] es menor o igual a 0, entonces el producto mínimo es dp[i - 1][1] * nums[i]; de lo contrario, dp[i - 1][1] es mayor que 0, dp[i - 1][0] es mayor, el producto mínimo debe ser el propio nums[i]
  2. Si nums[i] == 0, independientemente del número anterior, los productos máximo y mínimo deben ser 0
  3. Finalmente, nums[i] < 0, para obtener el producto máximo, debes multiplicar los números negativos, por lo que si dp[i - 1][1] es menor que 0, entonces el producto máximo es naturalmente dp[i - 1][1] * números[i];
public int maxProduct(int[] nums) {
    
    
        int len = nums.length;
        int max = nums[0];
        int[][] dp = new int[len][2];
        dp[0][0] = nums[0];
        dp[0][1] = nums[0];
        for(int i = 1;i < len;i++){
    
    
            if(nums[i] > 0){
    
    
                dp[i][0] = dp[i - 1][0] > 0 ? dp[i - 1][0] * nums[i] : nums[i];
                max = Math.max(max,dp[i][0]);
                dp[i][1] = dp[i - 1][1] < 0 ? dp[i - 1][1] * nums[i] : nums[i];
            }else if(nums[i] == 0){
    
    
                max = Math.max(max,0);
                dp[i][0] = 0;
                dp[i][1] = 0;
            }else{
    
    
                dp[i][0] = dp[i - 1][1] < 0 ? dp[i - 1][1] * nums[i] : nums[i];
                max = Math.max(max,dp[i][0]);
                dp[i][1] = dp[i - 1][0] > 0 ? dp[i - 1][0] * nums[i] : nums[i];
            }
        }
        return max;
    }

Supongo que te gusta

Origin blog.csdn.net/Pacifica_/article/details/126561604
Recomendado
Clasificación