回文
本文会先从LeetCode的第五题开始讲起,使用动态规划去解决这道题,然后进一步扩展到 “ 如果回文串是单链表数据结构,那么如果判断是否为回文串? ”。
也就是说,对于LeetCode第五题,我会用动态规划的方法去解决;
对于单链表回文题,我不仅会用动态规划的方法去解决,还会使用快慢指针法去解决。
一、动态规划解决LeetCode第五题
动态规划类题型都有着非常固定的解题步骤,如下图所示:
所以我们对应着进行三步的考虑:
第一步是去明确dp[i][j]的含义,这里如果字符串i~j是回文串,则dp[i][j] = True,否则为False。
第二步是找到二维dp数组之间的关系式,回文的特点即是首尾元素必定相等。所以我们可以进一步考虑:
如果dp[i+1][j-1]为True, 此时如果si = sj,则dp[i][j] = dp[i+1][j-1]。
注意这里的 i+1 和 j-1
dp[i][j] 若指代的字符串为 “abc”,则 dp[i+1][j-1] 指代的字符串为 “b”。
第三步是找到初始条件,先不考虑边界条件,字符串个数为1时,我们设其为True, 即 dp[i][i] = True。
那么 i+1 和 j-1 最终会逼近成为单个字符串,也就是说当 dp[i+1][j-1] 此时指代的字符串个数如果为1,我们设其为True。
dp[i+1][j-1]指代的字符串长度为(j-1)-(i+1)+1,其值需要小于2,所以可得j-i<3时,为单个字符串,此时的dp元素为True
接下来上动态规划核心判断回文的代码:
size = len(string)
# dp元素只是判断字符串是否为回文,
# 所以我们依旧要罗列所有的i、j可能性
for j in range(1, size):
for i in range(0, j):
if s[i] == s[j]: # 判断首尾
if j-i < 3: # 判断是否是单个字符串
dp[i][j] = True
else: # 如果不是单个字符串,则可继续逼近
dp[i][j] = dp[i+1][j-1]
# j之前的所有元素均已经存储在dp中了
else:
dp[i][j] = False
再上完整的动态规划解决最长回文子串问题的代码:
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
size = len(s)
if size < 2:
return s
dp = [[False for _ in range(size)] for _ in range(size)] # 二维dp数组
for i in range(size):
dp[i][i] = True
start = 0
max_len = 1 # 考虑输入"ac",返回"a"的情况,所以为1
for j in range(1, size):
for i in range(0, j):
if s[i] == s[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]:
if j-i+1 > max_len:
max_len = j-i+1
start = i
return s[start:start+max_len]
二、若是单链表形式的回文,那也仅仅只是存储数据方式的不同而已。如下图
三、使用快慢指针法判断单链表存储的字符串是否为“回文串”
刚刚都是使用动态规划来对字符回文串以及单链表回文去进行解决,这里使用快慢指针法对单链表回文进行解决。
设三个指针和一个隐藏指针,
prev = None
slow = head # 慢指针每次走一步,同时将逆序传入prev
fast = head # 快指针每次走两步,fast = fast.next.next
# 还有一个隐藏指针next,用于处理prev和slow的交互
需要分奇数和偶数来进行分别讨论,
若为奇数,则
第一次循环图解如下:
再来第二次循环,刚好可以仔细考虑下边界情况:
若考虑偶数的情况:
最后上完整的 快慢指针法判断单链表是否是回文 的核心代码:
if (head == None || head.next == None):
return True
prev = None
slow = head
fast = head
while (fast != None && fast.next != None): # 奇偶都考虑
fast = fast.next.next
ListNode next = slow.next
slow.next = prev
prev = slow
slow = next
# 跳出循环进入边界
if (fast != None): # 针对奇数情况的微调
slow = slow.next
while (slow != None): # 判断时奇偶都一样,slow指向Null时跳出循环
if (slow.val != prev.val):
return False
slow = slow.next
prev = prev.next
return True