【小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 两种编程语言进行解题

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

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


相关文章
|
2天前
leetcode代码记录(最长回文子串
leetcode代码记录(最长回文子串
7 2
|
5天前
|
存储 算法
Leetcode 30天高效刷数据结构和算法 Day1 两数之和 —— 无序数组
给定一个无序整数数组和目标值,找出数组中和为目标值的两个数的下标。要求不重复且可按任意顺序返回。示例:输入nums = [2,7,11,15], target = 9,输出[0,1]。暴力解法时间复杂度O(n²),优化解法利用哈希表实现,时间复杂度O(n)。
16 0
|
22天前
|
算法
代码随想录算法训练营第六十天 | LeetCode 84. 柱状图中最大的矩形
代码随想录算法训练营第六十天 | LeetCode 84. 柱状图中最大的矩形
20 3
|
22天前
|
存储 算法
代码随想录算法训练营第五十九天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
代码随想录算法训练营第五十九天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
22 1
|
22天前
|
算法
代码随想录算法训练营第五十七天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
代码随想录算法训练营第五十七天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
18 3
|
22天前
|
算法
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
34 1
|
22天前
|
算法
代码随想录算法训练营第五十五天 | LeetCode 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结
代码随想录算法训练营第五十五天 | LeetCode 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结
24 1
|
24天前
|
算法 API DataX
二叉树(下)+Leetcode每日一题——“数据结构与算法”“对称二叉树”“另一棵树的子树”“二叉树的前中后序遍历”
二叉树(下)+Leetcode每日一题——“数据结构与算法”“对称二叉树”“另一棵树的子树”“二叉树的前中后序遍历”
|
1天前
|
算法 数据安全/隐私保护 计算机视觉
基于二维CS-SCHT变换和LABS方法的水印嵌入和提取算法matlab仿真
该内容包括一个算法的运行展示和详细步骤,使用了MATLAB2022a。算法涉及水印嵌入和提取,利用LAB色彩空间可能用于隐藏水印。水印通过二维CS-SCHT变换、低频系数处理和特定解码策略来提取。代码段展示了水印置乱、图像处理(如噪声、旋转、剪切等攻击)以及水印的逆置乱和提取过程。最后,计算并保存了比特率,用于评估水印的稳健性。
|
2天前
|
存储 算法 数据可视化
基于harris角点和RANSAC算法的图像拼接matlab仿真
本文介绍了使用MATLAB2022a进行图像拼接的流程,涉及Harris角点检测和RANSAC算法。Harris角点检测寻找图像中局部曲率变化显著的点,RANSAC则用于排除噪声和异常点,找到最佳匹配。核心程序包括自定义的Harris角点计算函数,RANSAC参数设置,以及匹配点的可视化和仿射变换矩阵计算,最终生成全景图像。