最长回文子串-java

题目描述(力扣题库5): 给你一个字符串 s,找到 s 中最长的回文子串。如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 :

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

法一: 暴力匹配

首先定义一个方法isPalindrome(名称随意), 用来判断某个字符串是否是回文字符串,接着再一一例举出每个子字符串,利用该方法isPalindrome,找出回文字符串,然后进一步找出最长度的回文字符串,该方法遍历出所有的子字符串,时间复杂度为o(n^2), 判断是否为回文字符串,时间复杂度为o(n).所以, 总时间复杂度为o(n^3).

具体步骤如下:
  • 1.做一个特殊判断,如果字符串长度小于2(即字符串长度为1或0),直接返回该字符串
        if (str.length() < 2)
            return str;
  • 2.定义两个变量,begin(子字符串的起始位置), len(子字符串的长度), 这里,思考一下,为什么不定义一个字符串变量记录子字符串,而是利用变量 begin  len, 在原字符串上"截取"一段,作为子字符串.
        int begin = 0;
        int len = 1;
  • 3.遍历出所有子字符串,利用方法isPalindrome,找出回文字符串,如果该子字符串为回文字符串,进一步判断是否需要更新变量 begin len .
        for (int i = 0; i < str.length() - 1; i++) {
            for (int j = i + 1; j < str.length(); j++) {
                if (j - i + 1 > len && isPalindrome(str, i, j)) {
                    len = j - i + 1;
                    begin = i;
                }
            }
        }
  • 4.最后,返回最长的子字符串(利用 substring 方法)
return str.substring(begin, begin + len);
代码到这,已经结束了,以下是暴力匹配方法的完整代码(已在leetcode通过)
class Solution {
    public static boolean isPalindrome(String str, int left, int right) {
        while (left < right) {
            if (str.charAt(left) != str.charAt(right))
                return false;
            left++;
            right--;
        }
        return true;
    }

    public String longestPalindrome(String str) {
        // 1.暴力解法
        if (str.length() < 2)
            return str;

        int begin = 0;
        int len = 1;

        for (int i = 0; i < str.length() - 1; i++) {
            for (int j = i + 1; j < str.length(); j++) {
                if (j - i + 1 > len && isPalindrome(str, i, j)) {
                    len = j - i + 1;
                    begin = i;
                }
            }

        }
        return str.substring(begin, begin + len);
    }
}

法二: 动态规划(优化暴力匹配):
  •         创建一个dp[i][j] 表, 用来记录,下标i, j "截取"的子字符串两头的字符是否一样(str.charAt(i) 与 str.charAt(j) 是否相等), 如果相等的话, 我们进一步观察dp[i  + 1][j - 1]的值,如此往复,我们便能更加有效地找出回文字符串.
  •          首先,我们可以明确,下标 i , 一直小于等于下标 j ,并且当子字符串的长度小于 4 时,可以直接为dp[i][j] 赋值,并不需要观察dp[i  + 1][j - 1].
  •           先从列 j 开始遍历(1 str.length()- 原字符串的长度), 然后内循环遍历 i (0 j ), 给dp[i][j] 赋值, 这样当我们遇到子字符串的长度大于 4, 且 str.charAt(i) == str.charAt(j) 时, 就可以直接将dp[i + 1][ j - 1]的值赋给dp[i][j]. 
     
  具体步骤如下:     
  •    1.做一个特殊判断,如果字符串长度小于2(即字符串长度为1或0),直接返回该字符串
        if (str.length() < 2)
            return str;
  •    2.定义两个变量,begin(子字符串的起始位置), len(子字符串的长度), 这里,思考一下,为什么不定义一个字符串变量记录子字符串,而是利用变量 begin 和 len, 在原字符串上"截取"一段,作为子字符串.
        int begin = 0;
        int len = 1;
  •    3.创建dp表, 并为每个对角线元素赋值为true(当字符串长度为一是,该字符串肯定为回文字符串)
        boolean[][] dp = new boolean[str.length()][str.length()];
        for (int i = 0; i < str.length(); i++) {
            dp[i][i] = true;
        }

  •   4.外循环遍历 j (1 str.length()- 原字符串的长度), 然后内循环遍历 i (0 j ), 给dp[i][j] 赋值, 当str.charAt(i) != str.charAt(j), dp[i][j] = false;  else, 当子字符串的长度小于 4 (j - i < 3), dp[i][j] = true, 当子字符串的长度大于 4 (j - i > 3),观察dp[i + 1][ j - 1] 给 dp[i][j]值赋. (当然,也可以使用递归方法)
        for (int j = 1; j < str.length(); j++) {
            for (int i = 0; i < j; i++) {
                if(str.charAt(i) != str.charAt(j))
                    dp[i][j] = false;
                else {
                    if(j - i < 3){
                        dp[i][j] = true;

                    } else {
                        if(dp[i + 1][j - 1])
                            dp[i][j] = true;
                        else
                            dp[i][j] = false;
                    }
                }
                if(dp[i][j] && j - i + 1 > len){
                    len = j - i + 1;
                    begin = i;
                }
            }
        }
    4.最后,返回最长的子字符串(利用 substring 方法)
        return str.substring(begin, begin + len);
代码到这,已经结束了,以下是动态规划(暴力匹配优化)的完整代码(已在leetcode通过)(时间复杂度0(n^2))
class Solution {
    public String longestPalindrome(String str) {
        // 2.动态规划
        if(str.length() < 2)
            return str;

        int begin = 0;
        int len = 1;

        boolean[][] dp = new boolean[str.length()][str.length()];
        for (int i = 0; i < str.length(); i++) {
            dp[i][i] = true;
        }

        for (int j = 1; j < str.length(); j++) {
            for (int i = 0; i < j; i++) {
                if(str.charAt(i) != str.charAt(j))
                    dp[i][j] = false;
                else {
                    if(j - i < 3){
                        dp[i][j] = true;

                    } else {
                        if(dp[i + 1][j - 1])
                            dp[i][j] = true;
                        else
                            dp[i][j] = false;
                    }
                }
                if(dp[i][j] && j - i + 1 > len){
                    len = j - i + 1;
                    begin = i;
                }
            }
        }

        return str.substring(begin, begin + len);
    }
}

最后, 回到上文提出的问题 ---- "      思考一下,为什么不定义一个字符串变量记录子字符串,而是利用变量 begin  len, 在原字符串上"截取"一段,作为子字符串.     "

  1. 节省空间在内存中创建一个新的字符串变量记录子字符串会占用额外的空间。通过利用 begin  len 变量,可以直接在原字符串上确定最长回文子串的起始位置和长度,避免了额外的空间开销。

  2. 避免字符串拷贝如果每次找到最长回文子串后都创建一个新的字符串变量来存储子串,会涉及字符串的拷贝操作这会增加时间复杂度和空间开销。直接在原字符串上进行截取操作可以避免这种额外的开销。

  3. 简化逻辑通过 begin  len 变量,在最后直接使用 substring 方法截取子串,简化了代码逻辑,使得整体代码更加清晰和简洁。

虽然在某些情况下,创建一个新的字符串变量来存储子串可能会更直观,但在这段代码中,利用 begin 和 len 变量直接在原字符串上进行截取是为了节省空间、避免不必要的字符串拷贝,并简化代码逻辑。

以上就是本篇文章的全部内容,题目的讲解大量掺杂了博主自己的想法以及思路,文字讲述偏多,如若有不妥之处,敬请谅解.

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值