zoukankan      html  css  js  c++  java
  • leetcode-5-最长回文子串


    本题是leetcode,地址:5. 最长回文子串

    题目

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

    示例 1:

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

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

    分析

    题目还是很好理解的,思路有大致三种,一种暴力解、一种动态规划、一种是中心扩散法;

    暴力解决分析:根据回文子串的定义,枚举所有长度大于等于 2的子串,依次判断它们是否是回文,在枚举的过程中需要记录“当前子串的起始位置”和“子串长度”,最后通过substring截取一下对应的字符串就好了。很容发现,这种解法效率很低!时间复杂度:O(N^3),N是字符串的长度,枚举字符串的左边界、右边界,然后继续验证子串是否是回文子串,这三种操作都与 NN 相关;

    code-暴力解决

       public boolean check(String s, int left, int right) {
            while (left < right) {
                if (s.charAt(left) != s.charAt(right)) {
                    return false;
                }
                left++;
                right--;
            }
            return true;
        }
    
       public String longestPalindrome(String s) {
            if (s == null || s.length() < 1) {
                return "";
            }
            int start = 0, maxLenght = 1;
            char[] chars = s.toCharArray();
            for (int left = 0; left < chars.length; left++) {
                for (int right = chars.length - 1; right > left; right--) {
                    boolean check = check(s, left, right);
                    if (check && right - left + 1 > maxLenght) {
                        start = left;
                        maxLenght = right - left + 1;
                        break;
                    }
                }
            }
            return s.substring(start, start + maxLenght);
        }
    

    code-动态规划

    在设计动态规划时,需要考虑的点如下:

    思考状态
    思考状态转移方程
    思考初始化
    思考输出
    思考优化空间

    回文字符串的特点:

    如果一个字符串的头尾两个字符都不相等,那么这个字符串一定不是回文串;
    如果一个字符串的头尾两个字符相等,才有必要继续判断下去。
    如果里面的子串是回文,整体就是回文串;
    如果里面的子串不是回文串,整体就不是回文串。

    如果dp[i] [j] 表示子串 s[i..j] 是否为回文子串,这里子串 s[i..j] 定义为左闭右闭区间,可以取到 s[i]s[j]

    • 状态转换公式:
    dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
    

    边界条件:表达式 [i + 1, j - 1] 不构成区间,即长度严格小于 2,即 j - 1 - (i + 1) + 1 < 2 ,即 j - i < 3。怎么理解?即s[i..j] 的长度等于 2 或者等于 3 的时候,其实只需要判断一下头尾两个字符是否相等就可以直接下结论,而不需要参考之前的结果;

    • 初始化:单个字符一定是回文串,因此把对角线先初始化为 true,即 dp[i][i] = true
    • 输出:dp[i][j] = true,记录子串的长度和起始位置,当然我们是找最长的,需要跟上次记录的做比较;

    时间复杂度:O(N^2)

    根据以上信息就可以写我们的代码了

    
        public String longestPalindrome(String s) {
            int len = s.length();
            if (len < 2) {
                return s;
            }
            int begin = 0, maxLen = 1;
            // 状态记录
            boolean[][] dp = new  boolean[len][len];
            // 初始化:单个字符一定是回文串,因此把对角线先初始化为 `true`,即 `dp[i][i] = true` 
            for (int i = 0; i < len; i++) {
                dp[i][i] = true;
            }
            for (int j = 1; j < len ; j ++) {
                for (int i = 0; i < len; i ++) {
                    // 判断是否相等
                    if (s.charAt(i) != s.charAt(j)) {
                        dp[i][j] = false;
                    } else {
                        // 边界条件:即`s[i..j]` 的长度等于 `2` 或者等于 `3`
                        if (j - i < 3) {
                            dp[i][j] = true;
                        } else {
                            // 状态转换公式:dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
                            dp[i][j] = dp[i + 1][j - 1];
                        }
                    }
                    // 记录begin和长度
                    if (dp[i][j] && j - i + 1 > maxLen) {
                        begin = i;
                        maxLen = j - i + 1;
                    }
                }
            }
    
            return s.substring(begin, begin + maxLen);
        }
    

    code-中心扩散法

    暴力法采用双指针两边夹,验证是否是回文子串。如果枚举可能出现的回文子串的“中心位置”,从“中心位置”尝试尽可能扩散出去,得到一个回文串。遍历每一个索引,以这个索引为中心,利用“回文串”中心对称的特点,往两边扩散,看最多能扩散多远。枚举“中心位置”时间复杂度为 O(N),从“中心位置”扩散得到“回文子串”的时间复杂度为 O(N),时间复杂度可以降到 O(N^2)。

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

    image-20200718165946298

        public String longestPalindrome(String s) {
    
            int len = s.length();
            if (len < 2) {
                return s;
            }
            String res = "";
            int maxLength = 0;
            for (int i = 0; i < len; i++) {
                String one = around(s, i, i);
                String two = around(s, i, i + 1);
                String maxString = one.length() > two.length() ? one : two;
                if (maxString.length() > maxLength) {
                    res = maxString;
                    maxLength = res.length();
                }
            }
            return res;
        }
    
        public String around(String s, int left, int right) {
    
            while (left >= 0 && right < s.length()) {
                if (s.charAt(left) == s.charAt(right)) {
                    left--;
                    right++;
                } else {
                    break;
                }
            }
            return s.substring(left + 1, right);
        }
    

    你的鼓励也是我创作的动力

    打赏地址

  • 相关阅读:
    应用性能提升 要速度也要激情
    投票练习题
    租房子多条件查询练习
    JS时间戳格式化日期时间 由于mysql数据库里面存储时间存的是时间戳,取出来之后,JS要格式化一下显示。
    处理PHP字符串的10个简单方法;mysql出现乱码:character_set_server=utf8
    预定义数组(超全局数组)]
    静态方法
    类的构造方法和析构方法和封装的目的和封装的做法+访问修饰符
    面向对象与面向过程 $this的注意事项和魔术方法set和get
    PHP正则表达式;数组:for()遍历、 foreach ()遍历、each()list()组合遍历;指针遍历
  • 原文地址:https://www.cnblogs.com/yangsanchao/p/13336225.html
Copyright © 2011-2022 走看看