LeetCode[5] - Encuentre la subcadena de palíndromo más larga && programación dinámica && recursividad && enumeración de fuerza bruta

 Título -  [De LeetCode - 5] (Moderado)

Dada una cadena s, encuentre la subcadena palíndromo más larga en s, asumiendo que la longitud máxima de s no excede 1000.

consejos: cadena de palíndromo: tanto la lectura hacia adelante como hacia atrás son iguales

示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 同样是符合题意的答案。

示例 2:
输入: "cbbd"
输出: "bb"

texto

        Esta pregunta clásica está marcada como de dificultad media en LeetCode. Mientras puedas entender el significado de la pregunta, no es difícil de hacer, pero no es fácil escribir un algoritmo óptimo. De acuerdo con mis propias ideas para resolver problemas, resumí 3 tipos:

Método 1: enumeración de fuerza bruta

        Mi profesor dijo una vez: "No hay ningún algoritmo en el mundo que no pueda resolverse con dos bucles for". la cadena más larga de palíndromos.

    public String longestPalindrome(String s){
        if (s.isEmpty()) return null;
        if (s.length() == 1) return s;

        String ret =  s.substring(0,1);
        for (int i = 0; i < s.length(); i++) {
            for (int j = i+1; j < s.length(); j++) {
                String temp = s.substring(i,j+1);
                String rev = new StringBuilder(temp).reverse().toString();
                // 如果是回文串,且长度大于ret,替换
                if (temp.equals(rev) && temp.length() > ret.length()) {
                    ret = temp;
                }
            }
        }
        return ret;
    }

Ejecutado... El programa LeetCode muestra "límite de tiempo excedido" , ¡y la solución que es demasiado violenta no está permitida! !

Puede ejecutarlo en IDEA y ver, el método no es un problema.

 Método 2: Programación Dinámica

        Cuatro algoritmos de uso común: divide y vencerás, codicioso, retroceso y programación dinámica . Entre ellos, la programación dinámica debe ser el primero en ser dominado. A través de este problema de algoritmo, aprendamos juntos.

        Proceso de programación dinámica: cada decisión depende del estado actual, lo que a su vez provoca una transición de estado. Una secuencia de decisiones se genera en un estado cambiante, por lo que este proceso de toma de decisiones de optimización de múltiples etapas se denomina programación dinámica.

        La idea básica es similar al método divide y vencerás. También descompone el problema a resolver en varios subproblemas (etapas) y resuelve las subetapas en secuencia. La solución del primer subproblema proporciona información para la solución de este último subproblema.

        En esta pregunta, para la cadena str, creamos una nueva matriz de dos dígitos dp[][] arr como una nota para registrar los resultados de los subproblemas, donde dp[i][j] indica si el i-th cadena a j es un palíndromo, verdadero significa sí, falso significa no. Suponiendo que dp[i, j] = verdadero, entonces debe haber dp[i+1, j-1] = verdadero. De esta forma, la subcadena palíndromo más larga se puede descomponer en una serie de subproblemas, que se pueden resolver mediante programación dinámica. Esta es también la esencia de la programación dinámica, pero la complejidad algorítmica de este enfoque también es O(n^2)

        Primero construya la ecuación de transición de estado:

 La ecuación de transición de estado anterior dice, cuando str[i] = str[j]:

  • Si str[i+1...j-1] es un palíndromo, entonces str[i...j] también es un palíndromo;
  • Si str[i+1...j-1] no es un palíndromo, entonces str[i...j] tampoco es un palíndromo. 
    public String longestPalindrome(String s) {
        if (s.isEmpty()) return s;
        int len = s.length();
        boolean[][] dp = new boolean[len][len];
        int left = 0, right = 0;
        // 倒序向前遍历
        for (int i = len - 2; i >= 0; i--) {
            for (int j = i + 1; j < len; j++) {
                // 小于3一定是回文,aa或aba
                dp[i][j] = s.charAt(i) == s.charAt(j) && ( j-i < 3 || dp[i+1][j-1]);
                // 更新左右边界
                if(dp[i][j] && right-left < j-i){
                    left = i;
                    right = j;
                }
            }
        }
        return s.substring(left,right+1);
    }

Lo ejecuté... y el resultado no es malo.

 Los estudiantes cuidadosos encontrarán que el algoritmo anterior es conveniente en orden inverso, y el principio de orden positivo es el mismo, como sigue:

    public String longestPalindrome_1(String s){
        if (s.isEmpty()) return null;
        if (s.length() == 1) return s;
        int len = s.length();
        boolean[][] arr = new boolean[len][len];
        int left = 0, right = 0;
        // 正序向后遍历,注意i和j的位置
        for (int i = 1; i < len; i++) {
            for (int j = i - 1; j >= 0; j--) {
                arr[i][j] = s.charAt(i) == s.charAt(j) && ( i-j < 3 || arr[i-1][j+1]);
                if (arr[i][j] && (i - j > right - left)) {
                    right = i;
                    left = j;
                }
            }
        }
        return s.substring(left, right+1);
    }

Método 3: recursividad

Desde que presencié un accidente OOM en línea, rara vez uso la recursividad para resolver problemas. Parece simple, pero no solo consume tiempo sino también memoria. Sin embargo, en el siguiente método, solo utilicé la idea de recursividad para optimizar el primer método, y el punto de partida es "reducir consultas y juicios innecesarios para acortar el tiempo de operación".

Idea básica: el ciclo externo atraviesa una vez para determinar si los elementos en la posición simétrica de la posición de destino i son iguales. Si son iguales, el ciclo while toma el límite en ambos lados y guarda los valores del límite izquierdo y derecho; si son no son iguales, continúe desplazándose para reducir la recuperación innecesaria.

class Solution {
        
    private int stt = 0;
    private int end = 0;

    public String longestPalindrome_3(String s) {
        char[] str = s.toCharArray();
        find(str, 0);
        return s.substring(stt, end);
    }

    private void find(char[] str, int i) {
        if(i >= str.length) return;
        int left = i - 1;
        int right = i + 1;
        // 处理右侧连续的相同字符
        while(right < str.length && str[i] == str[right]) right++;
        // 重新定义下次循环的起点
        i = right;
        // 查找左右边缘
        while(left >= 0 && right < str.length && str[left] == str[right]) {
            left--;
            right++;
        }
        // 左边恢复到回文字符串端点
        left++;
        // 刷新记录
        if(right - left > end - stt) {
            stt = left;
            end = right;
        }
        find(str, i);
    }
}

Lo ejecuté por un tiempo... jojojojo, no esperaba vencer al 100% de los internautas. Este método de escritura es solo el más corto en tiempo y el consumo de memoria no está optimizado.


Supongo que te gusta

Origin blog.csdn.net/weixin_44259720/article/details/122988981
Recomendado
Clasificación