LC005-最长回文子串

最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

方法一:暴力匹配法

思路分析:

  • 根据回文子串的定义,美剧所有长度大于等于2的长度,依次判断他们是否是回文
  • 只针对当前得到的最长回文子串长度的子串进行"回文验证".
  • 在记录最长回文子串的时候,可以只记录“当前子串的起始位置”和“子串长度”,不必做截取。
package com.nie.Leec.exercise;

public class lee_005 {
    public static void main(String[] args) {
        String str = "babad";
        lee_005 a = new lee_005();
        System.out.println(a.longestPalindrome(str));
    }

    public String longestPalindrome(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        int maxLen = 1;
        int begin = 0;
        //先将字符串转换成数组
        char[] charArray = s.toCharArray();
        for (int i = 0; i < len - 1; i++) {   //第一个数子,当开始比如  babad中表示b 一直往后遍历一直到结束
            for (int j = i + 1; j < len; j++) {//在一次循环中,babad   第一个遍历到b 第二个循环就有其后紧邻的开始遍历 即b
                //j-i+1>maxLen判断是否比保存的长度长,,如果长,,进行判断是否是回文
                if (j - i + 1 > maxLen && validPalidromic(charArray, i, j)) {

                    maxLen = j - i + 1;//目前最长的回文的长度
                    begin = i;//回文开始的位置
                }
            }
        }
        return s.substring(begin, maxLen + begin);
    }

    private boolean validPalidromic(char[] charArray, int left, int right) {
        while (left < right) {
            if (charArray[left] != charArray[right]) {  //进行截取的
                return false;
            } else {
                left++;
                right--;
            }
        }
        return true;
    }
}

复杂度分析:

  • 时间复杂度:O(N^3),这里 NN 是字符串的长度,枚举字符串的左边界、右边界,然后继续验证子串是否是回文子串,这三种操作都与 NN 相关;
  • 空间复杂度:O(1),只使用到常数个临时变量,与字符串长度无关。

方法二:动态规划

主要思想:

主要思想:一旦在一个回文串的两端,对称的加上相同的元素,那么新形成的字符串仍然是一个回文串

方法:设定两个下标,从左到右扫描,一旦i和j下标重回,那么i下标移动的头部,从头开始扫描,j下标向后移动一个元素

当i下标和j下标指向的元素相同的时候,判断除去这两个相同元素剩下的字符串是否是回文串,如果是,那么最长回文串就是当前形成的新串,如果不是则继续扫描.

分布思路

  1. 先考虑题目的特解,本题的特解要考虑字符串为空串和字符串为1的情况,这两种情况都符合题意,所以直接返回字符串s。

  2. 声明两个变量maxLen为最大长度,begin为最大长度的回文的开始位置

  3. 二维数组dp[][],dp[i][j]为下标i到j的位置上的字符为回文 ,初始化的dp[i][i],因为单个字符串必然为回文

  4. 两重循环,j从第二个字符(下标为1)一直遍历到字符串末尾,同时保证 i从0遍历到j-1(i<j)。

  5. dp[i][j]为回文的判定条件第一个就是c[i] == c[j] ,符合这一条件的继续判定,不符合的直接置为false

  6. j-i<3,说明 j-i+1<4, 表明i到j没有4个字符,i到j中间只有0个或者1个字符,上一步已经判定c[i] == c[j],所以dp[i][j]必为回文,置为true。

  7. 边界条件为 j-i>=3,即j-i+1>=4,i到j至少有4个字符。这种情况下dp[i][j]要为回文,dp[i+1][j-1]必须为回文,即dp[i][j] = dp[i + 1][j - 1] 同正同错。

  8. 当dp[i][j]为回文且 i到j的字符串个数大于当前维护的最大长度,即 j-i+1>maxLen时候,需要把maxLen这个最大长度改为i到j的个数(j-i+1),开始位置begin改为i。

    public class Solution {
    
        public String longestPalindrome(String s) {
            // 特判
            int len = s.length();
            if (len < 2) {
                return s;
            }
    
            int maxLen = 1;
            int begin = 0;
    
            // dp[i][j] 表示 s[i, j] 是否是回文串
            boolean[][] dp = new boolean[len][len];
            char[] charArray = s.toCharArray();
    
            for (int i = 0; i < len; i++) {
                dp[i][i] = true;
            }
            for (int j = 1; j < len; j++) {
                for (int i = 0; i < j; i++) {
                    if (charArray[i] != charArray[j]) {
                        dp[i][j] = false;
                    } else {
                        //j-i<3,说明 j-i+1<4, 表明i到j没有4个字符,i到j中间只有0个或者1个字符,
                        //上一步已经判定c[i] == c[j],所以dp[i][j]必为回文,置为true。
                        if (j - i < 3) {
                            dp[i][j] = true;
                        } else {
                            // j-i>=3,即j-i+1>=4,i到j至少有4个字符。这种情况下dp[i][j]要为回文,
                            //dp\[i+1]\[j-1]必须为回文,即dp\[i]\[j] = dp\[i + 1]\[j - 1] 同正同错。
                            dp[i][j] = dp[i + 1][j - 1];
                        }
                    }
                    // 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置
                    if (dp[i][j] && j - i + 1 > maxLen) {
                        maxLen = j - i + 1;
                        begin = i;
                    }
                }
            }
            return s.substring(begin, begin + maxLen);
        }
    }
    

复杂度分析:

  • 时间复杂度:O(N^2);
  • 空间复杂度:O(N2),二维数组,因此空间复杂度为O(N2);

总结:

  • 用动态规划解题问题,有的时候并不是直接面向问题的
  • 动态规划依然是(空间换取时间)的思想的体现,而且本身动态规划就是一种打表格,就是空间换取时间
  • 动态规划的本质还是(暴力解法),因为需要枚举左右边界,有O(N2);

方法三:中心扩散法

文串一定是对称的,所以我们可以每次循环选择一个中心,进行左右扩展,判断左右字符是否相等即可。

在这里插入图片描述

  • 奇数回文串的“中心”是一个具体的字符,例如:回文串 “aba” 的中心是字符 “b”;
  • 偶数回文串的“中心”是位于中间的两个字符的“空隙”,例如:回文串串 “abba” 的中心是两个 “b” 中间的那个“空隙”。
public class Solution {

    public String longestPalindrome(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        int maxLen = 1;
        String res = s.substring(0, 1);
        // 中心位置枚举到 len - 2 即可
        for (int i = 0; i < len - 1; i++) {
            String oddStr = centerSpread(s, i, i);
            String evenStr = centerSpread(s, i, i + 1);
            String maxLenStr = oddStr.length() > evenStr.length() ? oddStr : evenStr;
            if (maxLenStr.length() > maxLen) {
                maxLen = maxLenStr.length();
                res = maxLenStr;
            }
        }
        return res;
    }

    private String centerSpread(String s, int left, int right) {
        // left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数//左边
        // right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
        int len = s.length();
        int i = left;
        int j = right;
        while (i >= 0 && j < len) {
            if (s.charAt(i) == s.charAt(j)) {
                i--;
                j++;
            } else {
                break;
            }
        }
        // 这里要小心,跳出 while 循环时,恰好满足 s.charAt(i) != s.charAt(j),因此不能取 i,不能取 j
        return s.substring(i + 1, j);
    }
}

复杂度分析

  • 时间复杂度:O(N2),枚举"中心位置"时间复杂度O(N),由中心位置扩展到回文O(N),所以为O(N2)
  • 空间复杂度O(1),只使用到常数个临时变量,与字符串长度无关。
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页