刻意练习:LeetCode实战 -- Task17. 最长回文子串

背景

本篇图文是LSGO软件技术团队组织的 第二期基础算法(Leetcode)刻意练习训练营 的打卡任务。本期训练营采用分类别练习的模式,即选择了五个知识点(数组、链表、字符串、树、贪心算法),每个知识点选择了 三个简单、两个中等、一个困难 等级的题目,共计三十道题,利用三十天的时间完成这组刻意练习。

本次任务的知识点:字符串

字符串或串(string) 是由数字、字母、下划线组成的一串字符。一般记为 s = “a1a2...an”(n >= 0)。它是编程语言中表示文本的数据类型。

通常以串的整体作为操作对象,如:在串中查找某个子串在该串中首次出现的位置、在串的某个位置上插入一个子串以及删除一个子串等。两个字符串相等的充要条件是:长度相等,并且各个对应位置上的字符都相等。串通常以顺序的方式进行存储与实现。


题目

  • 题号:5
  • 难度:中等
  • https://leetcode-cn.com/problems/longest-palindromic-substring/

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

示例 1

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

示例 2

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

示例 3

输入: "a"
输出: "a"

实现

回文是一个正读和反读都相同的字符串,例如,“aba”是回文,而“abc”不是。

第一种:暴力法,列举所有的子串,判断该子串是否为回文

  • 执行结果:超出时间限制
public class Solution
{
    public string LongestPalindrome(string s)
    {
        if (string.IsNullOrEmpty(s))
            return string.Empty;
        if (s.Length == 1)
            return s;

        int start = 0;
        int end = 0;
        int len = int.MinValue;
        for (int i = 0; i < s.Length; i++)
        {
            for (int j = i + 1; j < s.Length; j++)
            {
                string str = s.Substring(i, j - i + 1);
                if (isPalindrome(str) && str.Length > len)
                {
                    len = str.Length;
                    start = i;
                    end = j;
                }
            }
        }
        return s.Substring(start, end - start + 1);
    }

    public bool isPalindrome(string s)
    {
        for (int i = 0, len = s.Length / 2; i < len; i++)
        {
            if (s[i] != s[s.Length - 1 - i])
                return false;
        }
        return true;
    }
}

第二种:动态规划

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间

我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。

具体的动态规划算法多种多样,但它们具有相同的填表格式

使用记号 s[l, r] 表示原始字符串的一个子串,lr 分别是区间的左右边界的索引值,使用左闭、右闭区间表示左右边界可以取到。

dp[l, r] 表示子串 s[l, r](包括区间左右端点)是否构成回文串,是一个二维布尔型数组。

  • 当子串只包含 1 个字符,它一定是回文子串;
  • 当子串包含 2 个以上字符的时候:s[l, r] 是一个回文串,那么这个回文串两边各往里面收缩一个字符(如果可以的话)的子串 s[l + 1, r - 1] 也一定是回文串。

故,当 s[l] == s[r] 成立的时候,dp[l, r]的值由 dp[l + 1, r - l] 决定,这里还需要再多考虑一点点:“原字符串去掉左右边界”的子串的边界情况。

  • 当原字符串的元素个数为 3 的时候,如果左右边界相等,那么去掉它们以后,只剩下 1 个字符,它一定是回文串,故原字符串也一定是回文串;
  • 当原字符串的元素个数为 2 的时候,如果左右边界相等,那么去掉它们以后,只剩下 0 个字符,显然原字符串也一定是回文串。

综上,如果一个字符串的左右边界相等,判断为回文只需以下二者之一成立即可:

  • 去掉左右边界以后的字符串不构成区间,即s[l + 1, r - 1]包含元素少于2个,即:r - l <= 2
  • 去掉左右边界以后的字符串是回文串,即dp[l + 1, r - 1] == true

  • 状态:通过
  • 103 / 103 个通过测试用例
  • 执行用时: 232 ms, 在所有 C# 提交中击败了 46.79% 的用户
  • 内存消耗: 40.9 MB, 在所有 C# 提交中击败了 5.43% 的用户
public class Solution {
    public string LongestPalindrome(string s) 
    {
        if (string.IsNullOrEmpty(s))
            return string.Empty;
        int len = s.Length;
        if (len == 1)
            return s;
        int longestPalindromelen = 1;
        string longestPalindromeStr = s.Substring(0, 1);
        bool[,] dp = new bool[len, len];

        for (int r = 1; r < len; r++)
        {
            for (int l = 0; l < r; l++)
            {
                if (s[r] == s[l] && (r - l <= 2 || dp[l + 1, r - 1] == true))
                {
                    dp[l, r] = true;
                    if (longestPalindromelen < r - l + 1)
                    {
                        longestPalindromelen = r - l + 1;
                        longestPalindromeStr = s.Substring(l, r - l + 1);
                    }
                }
            }
        }
        return longestPalindromeStr;        
    }
}

Python 语言

  • 执行结果:通过
  • 执行用时:3392 ms, 在所有 Python3 提交中击败了 43.87% 的用户
  • 内存消耗:21.2 MB, 在所有 Python3 提交中击败了 18.81% 的用户
class Solution:
    def longestPalindrome(self, s: str) -> str:
        count = len(s)
        if count == 0 or count == 1:
            return s
        longestPalindromelen = 1
        longestPalindromeStr = s[0:1]
        dp = [[False] * count for i in range(count)]
        for r in range(1, count):
            for l in range(0, r):
                if s[r] == s[l] and (r - l <= 2 or dp[l + 1][r - 1] == True):
                    dp[l][r] = True
                    if longestPalindromelen < r - l + 1:
                        longestPalindromelen = r - l + 1
                        longestPalindromeStr = s[l:l + longestPalindromelen]
        return longestPalindromeStr

参考文献

  • https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/

往期活动

LSGO软件技术团队会定期开展提升编程技能的刻意练习活动,希望大家能够参与进来一起刻意练习,一起学习进步!

  • Python基础刻意练习活动即将开启,你参加吗?
  • Task01:变量、运算符与数据类型
  • Task02:条件与循环
  • Task03:列表与元组
  • Task04:字符串与序列
  • Task05:函数与Lambda表达式
  • Task06:字典与集合
  • Task07:文件与文件系统
  • Task08:异常处理
  • Task09:else 与 with 语句
  • Task10:类与对象
  • Task11:魔法方法
  • Task12:模块

我是 终身学习者“老马”,一个长期践行“结伴式学习”理念的 中年大叔

我崇尚分享,渴望成长,于2010年创立了“LSGO软件技术团队”,并加入了国内著名的开源组织“Datawhale”,也是“Dre@mtech”、“智能机器人研究中心”和“大数据与哲学社会科学实验室”的一员。

愿我们一起学习,一起进步,相互陪伴,共同成长。

后台回复「搜搜搜」,随机获取电子资源!
欢迎关注,请扫描二维码:

你可能感兴趣的:(C#学习,数据结构与算法,Python学习)