LeetCode[5] - 求最长回文子串 && 动态规划 && 递归 && 暴力枚举

 题目 - 【源于LeetCode - 5】(中等)

给定一个字符串 s,找到 s 中最长的回文子串,假设 s 的最大长度不超过1000。

tips: 回文字符串:正读反读都一样

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

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

正文

        这道经典的题目在 LeetCode 中被标记为中等难度,只要能理解题目意思做出来难度不大,但是,想写出一个最优算法并不容易。按照我自己的解题思路,总结了3种:

方法一:暴力枚举

        我的老师曾说过:“世界上没有2个for循环解决不了的算法”,基于冒泡排序的理念,我最先想到的也是最暴力的方法,就是枚举出所有的子字符串,最终确定最长的那个回文数串。

    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;
    }

执行了一下...... LeetCode 程序显示“超出时间限制”,果然太暴力的解法不被允许啊!!

大家可以再IDEA中运行看看,方法是没问题的。

 方法二:动态规划

        四大常用算法:分治、贪心、回溯、动态规划,其中,动态规划是最应该第一个被掌握的,借着这道算法题,我们一起来学习一下。

        动态规划过程:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

        基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。

        本题中,对于字符串str,我们新建一个二位数组 dp[][] arr 来作为备忘录,记录子问题的结果,其中 dp[i][j] 表示字符串第 i 到 j 是否为回文, true 表示是, false 表示不是。假设 dp[i, j] = true ,那么必定存在 dp[i+1, j-1] = true。这样最长回文子串就能分解成一系列子问题,可以利用动态规划求解了。这也是动态规划的精髓所在,不过这种做法的算法复杂度也是O(n^2)

        首先构造状态转移方程:

 上面的状态转移方程表示,当 str[i]  = str[j] 时:

  • 如果 str[i+1...j-1] 是回文串,则 str[i...j] 也是回文串;
  • 如果 str[i+1...j-1] 不是回文串,则 str[i...j] 也不是回文串。 
    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);
    }

执行了一下...... 结果还不错。

 细心的同学会发现,上面的算法是倒序便利的,正序原理也是一样的,如下:

    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);
    }

方法三:递归

自从见证过一次线上OOM事故以后,我就很少使用递归来解决问题,看似简洁,实际上不仅耗时而且耗内存。但是,在下面的方法中,我只是运用了递归的思想将方法一进行了优化,出发点就是“减少不必要的查询和判断,以缩短运算时间”。

基本思路:外层循环遍历一次,判断以目标位置 i 对称位置的元素是否相等,若相等 while 循环向两边取到边界,保存左右边界值;不相等则继续遍历,以此来减少不必要的取值操作。

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);
    }
}

执行了一下...... 嚯嚯嚯,没想到居然打败了100%的网友。这种写法只是时间上最短,内存消耗并没有优化。


猜你喜欢

转载自blog.csdn.net/weixin_44259720/article/details/122988981