回文字符串&最长回文子串和子序列 - Python

Palindrome 回文字符串就是指从前往后和从后往前读,都是一样的,比如“aabcbaa”。
注意区分子串和子序列,子串是连续的,子序列可以不连续


题型1:判断字符串是否为回文字符串

方法:双指针

思路:

同时从字符串头尾开始向中间扫描字串,如果所有字符都一样,那么这个字串就是一个回文。采用这种方法的话,我们只需要维护头部和尾部两个扫描指针即可。

代码如下:

def isPalindrome(s):
    if len(s) < 1:
        return False
    if len(s) == 1:
        return True
    
    front = 0
    back = len(s) - 1
    while front < back:
        if s[front] != s[back]:
            return False
        else:
            front += 1
            back -= 1
       
    return True

题型2:求字符串的最长回文子序列长度

方法:动态规划

思路:

这里我们定义一个二维数组dp:

dp[i][j] 表示字符串 s[i ... j] 的最长回文子序列的长度。

image.png

计算dp[i][j] 分两种情况:

  1. s[i] == s[j]:
    dp[i][j] = dp[i+1][j-1] + 2

  2. 当s[i] != s[j]:
    dp[i][j] = max(dp[i][j-1], dp[i+1][j])


    image.png

    image.png

代码如下:

def longestPalindromeSubseq(s):
    n = len(s)
    dp = [ [0]*n for _ in range(n)]
    
    for i in range(n):
        dp[i][i] = 1
    
    for i in range(n-1, -1, -1):
        for j in range(i+1, n):
            if s[i] == s[j]:
                dp[i][j] = dp[i+1][j-1] + 2
            else:
                dp[i][j] = max(dp[i][j-1], dp[i+1][j])

    return dp[0][n-1]
image.png

题型3:打印字符串的最长回文子序列

方法:动态规划

思路:

在上一题的基础上,多加一个path的矩阵来跟踪哪些字母被选中,加到回文子序列中。
规则如下:

  1. 如果是两头相等的情况:标为2
    之后回过来找字母的时候,往左下角找
  2. 如果是“去头”的情况(即dp[i][j] = dp[i+1][j]):标为1
    之后找字母的时候,往正下方找
  3. 如果是“去尾”的情况(即dp[i][j] = dp[i][j-1]):标为0
    之后找字母的时候,往左边找

(见下面的示意图)


image.png

代码如下:

def printlongestPalindromeSubseq(s):
    n = len(s)
    dp = [ [0]*n for _ in range(n)]
    path = [ [None]*n for _ in range(n) ]
    
    for i in range(n):
        dp[i][i] = 1
    
    for i in range(n-1, -1, -1):
        for j in range(i+1, n):
            if s[i] == s[j]:
                dp[i][j] = dp[i+1][j-1] + 2
                # 如果是相等,标记为2
                path[i][j] = 2
            else:
                dp[i][j] = max(dp[i][j-1], dp[i+1][j])
                # 如果是去头,path上标记为0,如果是去尾标记为1
                if dp[i][j] == dp[i+1][j]:  # 去头
                    path[i][j] = 1
                elif dp[i][j] == dp[i][j-1]:  # 去尾
                    path[i][j] = 1
    
    # 从dp[0][n-1]开始往反方向走:
    i = 0
    j = n-1
    ans = []
    while i <= n-1 and j >= 0 and path[i][j] != None:
        print(i, j)
        if path[i][j] == 2: # 往左下角移动一格
            i += 1
            j -= 1
            ans.append(s[i])
        elif path[i][j] == 1: # 往左边移动一格
            j -= 1
            ans.append(s[i])
        elif path[i][j] == 0:  # 往正下方移动一格
            i += 1
            ans.append(s[i])

    return (''.join(str(s) for s in ans))

题型4:打印字符串的最长回文子串和长度

方法:动态规划

Examples:

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

示例 1:

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

输入: “cbbd”
输出: “bb”

思路:

  • 对于字符串长度大于2的情况:
    对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道“bab” 是回文串,那么 “ababa” 一定是回文串,这是因为它的首尾两个字母都是“a”。

    根据这样的思路,用动态规划来解决问题。
    用P(i, j)表示字符串s的第i个到第j个字母组成的串(表示为s[i : j])是否为回文串:


    image.png

    于是,可以得到动态规划的状态转移方程:
    P(i, j) = P(i+1, j-1), if (Si == Sj)
    也就是说,只有s[i+1: j-1]是回文串,且s的第i个和第j个字母相同时,s[i: j]才会是回文串。

  • 对于字符串长度为1或者2的情况:
    如果长度为1,那么肯定是回文串,直接可返回本身;
    如果长度为2,只有两个字母相同时,才是回文串,反之则返回第一个字母
    这就是这个问题的边界条件。

最终的答案就是P(i, j) = True中 j - i + 1(即子串长度)的最大值。
注意:在转移状态方程中,是从长度较短的字符串向长度较长的字符串进行转移的,因此要注意动态规划的循环循序。


image.png

代码如下:

def longestPalindrome(s):
        n = len(s)
        if n < 2:
            return s
        
        dp = [ [False]*n for _ in range(n) ]
        
        max_len = 1
        start = 0
        
        for i in range(n): #矩阵对角线全部为True
            dp[i][i] = True
            
        for j in range(1, n):
            print('j', j)
            for i in range(0, j):
                print('i', i)
                if s[i] == s[j]:
                    # 当第i个和第j个之间只隔一个字母或没有字母时:
                    if j - i < 3: 
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i+1][j-1]
                else:
                    dp[i][j] = False
                    
                if dp[i][j]:
                    cur_len = j-i+1
                    if cur_len > max_len:
                        max_len = cur_len
                        start = i
        
        print('length: ', max_len)
        return s[start : start + max_len]

PS:个人笔记总结,供之后复习使用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343