【小Y学算法】⚡️每日LeetCode打卡⚡️——5.最长回文子串

简介: 📢前言🌲原题样例回文串含义🌻C#方法一:暴力法🌻C#方法二:中心扩展法🌻Java方法一:动态规划🌻Java方法一:中心扩展算法💬总结

📢前言

🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻

🌲 每天打卡一道算法题,既是一个学习过程,又是一个分享的过程😜

🌲 提示:本专栏解题 编程语言一律使用 C# 和 Java 两种进行解题

🌲 要保持一个每天都在学习的状态,让我们一起努力成为算法大神吧🧐!

🌲 今天是力扣算法题持续打卡第5天🎈!

🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻

🌲原题样例

给你一个字符串 s,找到 s 中最长的回文子串。


示例 1:


输入:s = “babad”

输出:“bab”

解释:“aba” 同样是符合题意的答案。


示例 2:


输入:s = “cbbd”

输出:“bb”


示例 3:


输入:s = “a”

输出:“a”


示例 4:


输入:s = “ac”

输出:“a”


提示:


1 <= s.length <= 1000

s 仅由数字和英文字母(大写和/或小写)组成

回文串含义

这里简单解释一下什么是回文串~


"回文串”就是一个正读和反读都一样的字符串,比如“mom”或者“noon”等等就是回文串。


顾名思义,“回文子串”的意思是一个字符串中的回文串,比如字符串“baba”中就包含有“bab”和“aba”这两个回文子串


🌻C#方法一:暴力法

解题思路


首先我们会想到使用 暴力法 来解决题目,用3层循环来对每个子串进行检查,最后取最长的子串作为结果,这样时间复杂度为 O(n^3) 。

然后可能会考虑到使用动态规划的方式,以空间来换取时间,可以将时间复杂度优化为 O(n^2),但相应的空间复杂度会增大

public class Solution {
    public string LongestPalindrome(string s)
    {
        string result = "";
        int n = s.Length;
        for (int i = 0; i < n; i++)
        {
            for (int j = i; j < n; j++)
            {
                // 检查 s[i]到s[j]是否是回文串,如果是,且长度大于result长度,就更新它
                int p = i, q = j;
                bool isPalindromic = true;
                while (p < q)
                {
                    if (s[p++] != s[q--])
                    {
                        isPalindromic = false;
                        break;
                    }
                }
                if (isPalindromic)
                {
                    int len = j - i + 1;
                    if (len > result.Length)
                    {
                        result = s.Substring(i, len);
                    }
                }
            }
        }
        return result;
    }
}
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/leetcode-5-longest-palindromic-substring-zui-chang/

执行结果

执行结果 通过,执行用时 1708ms,内存消耗 26.5 MB


复杂度分析


时间复杂度: O(n^3)

空间复杂度:O(1)


🌻C#方法二:中心扩展法

对于回文串,我们可以找到一个中心,从这个中心向两边扩展的话,两边对应的值是相等的。按照这个逻辑,我们只需要一层主循环 i 将 s 遍历一遍即可,并在循环内部 将s[i]视为中心 使用中心扩展法来求出以s[i]为中心的最长的回文串;当i将s遍历完后,即可得到s的最长回文串。


下面我们以 s=“abcbc”, 且 i==2 为例,讨论一下如何进行中心扩展。


i==2指向c,我们初始化两个指针p与q都指向这个c

p–,q++,p指向了左边b,q指向了右边b

因为s[p]==s[q], 所以再次执行p–,q++,此时p指向了最左边a,q指向了最右边c

因为s[p]!=s[q],所以结束扩展。

此时,我们得到了当i指向中间c时的最长回文子串为"bcb",长度为 q-p-1,开始位置为p+1. 对应图解图下:

image.png

当子串长度为奇数时,这个逻辑很容易理解; 当子串长度为偶数时,比如 “abba”,我们需要把中心理解为中轴线,即中轴线在两个b的中间。


将中心理解为中轴线后,中轴线既可以在整数位上,例如"aba"中的b,也可以在两数之间,例如"abba"中的bb之间。若我们用mid表示中轴线,则mid可以等于0、1、2… 也可以等于0.5、1.5、2.5… 对于长度为n的数组,中轴线的选择有 n 个,i 的取值为0到 2(n-1) .。


代码为:

public class Solution {
    public string LongestPalindrome(string s)
    {
        string result = "";
        int n = s.Length;
        int end = 2 * n - 1;
        for (int i = 0; i < end; i ++)
        {
            double mid = i / 2.0;
            int p = (int)(Math.Floor(mid));
            int q = (int)(Math.Ceiling(mid));
            while (p >= 0 && q < n)
            {
                if (s[p] != s[q]) break;
                p--; q++;
            }
            int len = q - p - 1;
            if (len > result.Length)
                result = s.Substring(p + 1, len);
        }
        return result;
    }
}
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/leetcode-5-longest-palindromic-substring-zui-chang/

执行结果

执行结果 通过,执行用时88ms,内存消耗 26 MB


复杂度分析


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

空间复杂度:O(1)


🌻Java方法一:动态规划

Java方法也是力扣官方对此题的解答,可以参考答案

image.png

根据这个思路,我们就可以完成动态规划了,最终的答案即为所有 P(i, j) = \text{true}P(i,j)=true 中 j-i+1j−i+1(即子串长度)的最大值。注意:在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。

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];
        // 初始化:所有长度为 1 的子串都是回文串
        for (int i = 0; i < len; i++) {
            dp[i][i] = true;
        }
        char[] charArray = s.toCharArray();
        // 递推开始
        // 先枚举子串长度
        for (int L = 2; L <= len; L++) {
            // 枚举左边界,左边界的上限设置可以宽松一些
            for (int i = 0; i < len; i++) {
                // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                int j = L + i - 1;
                // 如果右边界越界,就可以退出当前循环
                if (j >= len) {
                    break;
                }
                if (charArray[i] != charArray[j]) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    begin = i;
                }
            }
        }
        return s.substring(begin, begin + maxLen);
    }
}

执行结果

执行结果 通过,执行用时178ms,内存消耗 42.9 MB


复杂度分析


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

空间复杂度:O(n^2)


🌻Java方法一:中心扩展算法

思路与算法

image.png

可以发现,所有的状态在转移的时候的可能性都是唯一的。也就是说,我们可以从每一种边界情况开始「扩展」,也可以得出所有的状态对应的答案。


边界情况即为子串长度为 11 或 22 的情况。我们枚举每一种边界情况,并从对应的子串开始不断地向两边扩展。如果两边的字母相同,我们就可以继续扩展,例如从 P(i+1,j-1)P(i+1,j−1) 扩展到 P(i,j)P(i,j);如果两边的字母不同,我们就可以停止扩展,因为在这之后的子串都不能是回文串了。


聪明的读者此时应该可以发现,「边界情况」对应的子串实际上就是我们「扩展」出的回文串的「回文中心」。方法二的本质即为:我们枚举所有的「回文中心」并尝试「扩展」,直到无法扩展为止,此时的回文串长度即为此「回文中心」下的最长回文串长度。我们对所有的长度求出最大值,即可得到最终的答案。

class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() < 1) {
            return "";
        }
        int start = 0, end = 0;
        for (int i = 0; i < s.length(); i++) {
            int len1 = expandAroundCenter(s, i, i);
            int len2 = expandAroundCenter(s, i, i + 1);
            int len = Math.max(len1, len2);
            if (len > end - start) {
                start = i - (len - 1) / 2;
                end = i + len / 2;
            }
        }
        return s.substring(start, end + 1);
    }
    public int expandAroundCenter(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            --left;
            ++right;
        }
        return right - left - 1;
    }
}

执行结果

执行结果 通过,执行用时38ms,内存消耗 30.8 MB


复杂度分析


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

空间复杂度:O(1)


💬总结

今天是力扣算法题打卡的第五天!今天的题有点难,借助力扣大神题解看了半天!

文章采用 C# 和 Java 两种编程语言进行解题

一些方法也是参考力扣大神写的,也是边学习边分享,再次感谢算法大佬们

那今天的算法题分享到此结束啦,明天再见!


相关文章
|
1月前
Leetcode第五题(最长回文子串)
这篇文章介绍了解决LeetCode第五题“最长回文子串”问题的一种方法,使用了中心扩展法来寻找给定字符串中的最长回文子串。
34 0
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
15天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
1月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
27 2
|
3月前
|
算法
测试工程师的技能升级:LeetCode算法挑战与职业成长
这篇文章通过作者亲身体验LeetCode算法题的过程,探讨了测试工程师学习算法的重要性,并强调了算法技能对于测试职业成长的必要性。
70 1
测试工程师的技能升级:LeetCode算法挑战与职业成长
|
3月前
|
算法
LeetCode第5题最长回文子串
该文章介绍了 LeetCode 第 5 题最长回文子串的解法,通过分析回文子串的特点,使用动态规划的思想,用二维数组记录字符串是否为回文串,从后往前遍历找子串,减少重复判断,最终找到最长回文子串,并总结了解题时通过举例推导找规律的思路。
LeetCode第5题最长回文子串
|
3月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
52 6
|
3月前
|
存储 算法 Java
LeetCode经典算法题:打家劫舍java详解
LeetCode经典算法题:打家劫舍java详解
70 2
|
3月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
50 1
|
3月前
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
61 1
下一篇
无影云桌面