LeetCode 214 最短回文串 KMP的应用 Python3

原题:链接

给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。
LeetCode 214 最短回文串 KMP的应用 Python3_第1张图片
先简单分析一下,本题需要返回最短的回文串,我们一个自然的想法是源字符串中有没有回文字符串呢?如果有的话,就把它单拎出来,然后以其为轴进行y轴的翻转。极端地情况,如果源字符串不存在回文,就像示例2所示,则固定第一个字符a(很明显,单个字符本身满足回文的特点),将其余的字符串以a翻过山和大海,成为dcbabcd。首先使用一种暴力法,即从字符串末尾依次考察其是否为回文串,如果是则将其单拎出来,设源字符串为s,剩余的字符串设为remain,在纸上画一画即可验证返回结果就等于:

reverse(remain) + s
代码如下:
def shortestPalindrome(s):
	n = len(s)
	rev = s[::-1]
	for i in range(n):
		if s[:n-i] == rev[i:]:
			return rev[:i] + s
	return ''

其实,我们可以尝试着用双指针的思想来处理回文。结合上面的分析,我们需要找到存在的回文串,而那些不可能存在回文的字符则直接将其拼接到结果上。具体做法就是,首先设定一个指向字符串头和尾的指针ij,其中i表示一个待进一步确定的回文串的上界,即s[:i]可能还存在回文串,而j就是一个不断从尾到头迭代的指针。迭代过程中,一旦满足s[i] == s[j],我们有理由将指针i向前移动一位。迭代循环一次之后,将可能存在回文串的字符串递归重复进行,最后即可得到我们的结果。极端的例子,如果源字符串本身就是回文串,则遍历结果就是i遍历完了整个字符串,因此就直接返回该字符串,这也是递归结束的地方,如果源字符串为abcd,很明显,i最后指向1,即只有a存在回文串,并将剩下的字符串进行拼接成dcb+ shortestPalindrome('a') + bcd

代码如下:

def shortestPalindrome(s):
	n = len(s)
	i = 0
	for j in range(n-1, -1, -1):
		if s[i] == s[j]:
            i += 1
    # 递归结束
    if i == n:
        return s
    temp = s[i:]
	return temp[::-1] + shortestPalindrome(s[:i]) + temp

上面两种解法的时间复杂度都达到了O(n^2),我们试想有没有存在一种线性时间复杂度的算法。

答案是肯定的,只是我们需要捋一捋如何想到使用KMP来解决这个问题。KMP算法是一个有名的字符串匹配算法,常用于在一个文本串S中找到模式串P。而理解KMP算法的核心就是要理解公共前后缀和一个部分匹配表(Partial Match Table),其核心就在于如何通过模式串P来构造PMT,PMT保存着一些特殊的索引,其用途在于加速选择失配位置处的跳转索引值。这只是我的一种抽象理解,可能表述不太清楚,关于KMP的算法,可以参考这个链接,本文程序就基于此思路构造next数组。

理解了KMP算法,我们接着来思考一下KMP能解决本题的什么问题。从第一个暴力算法我们得知,我们就是不断地迭代寻找满足回文的串,即

if s[:n-i] == rev[i:]:
    return rev[:i] + s

如果我们将源字符串和反向的源字符串拼接在一起,成为一个新的字符串new_str,这就变成了一个查找new_str最长公共前后缀的问题了。

具体做法是建立一个new_strnext表,其表中的值代表着该索引失配下需要跳转的索引值。因此next_strnext表的最后一个值k就表示源字符串的前k+1个字符串全是回文,然后提取思路和暴力法相同。此外,在构造new_str的时候需要添加分隔符,即new_str = s + '*' + s[::-1],如果没有分隔符’*’,则字符串会发生混淆以至于得不到正确的最长前后缀长度。代码如下:

def shortestPalindrome(self, s: str) -> str:
    # KMP
    rev = s[::-1]
    new_str = s + '*' + rev
    def getNext(p):
        # KMP得到next数组的程序
        n = len(p)
        i, j = 0, -1
        next = [0] * n
        next[0] = -1
        while i < n-1:
            if j == -1 or p[i] == p[j]:
                i += 1
                j += 1
                next[i] = j
            else:
                j = next[j]
        return next
    next = getNext(new_str)
    N = len(s)
    # rev[: N-(next[-1]+1)] 就和方法一的rev[:i]一个道理
    return rev[: N-(next[-1]+1)] + s

寒假回家,天天都不想看书o(╥﹏╥)o。

晚来天欲雪,能饮一杯无?

你可能感兴趣的:(LeetCode刷题)