此题判断子序列是否为回文串很重要,能省很多事情。一种方法就是用dp得出,另一种是记忆化搜索。得出结果后,可以用两个数组和传统dfs来得到所有可能的拆分结果。
dp中,两个点要注意:正常状态要看左下角,即左索引+1,右索引-1的情况,所以需要我们从上往下建表,需要用到先i后j,用j,i进行索引,这个是个很强的技巧。第二个点是找到连续的相同的数,这点的处理也很棒。处理的规则是:只关注连续的两个。因为两个找到后,后续不管是三个四个都可以用左右扩张的方式得到。
class Solution:
def partition(self, s: str) -> List[List[str]]:
# 预处理,得到子序列是否为回文串结果
dp1 = [[0 for _ in s] for _ in s]
for i in range(len(s)):
dp1[i][i] = 1
for j in range(i):
if s[i] == s[j] and (dp1[j+1][i-1] or i == j+1):
dp1[j][i] = 1
# dfs记忆化搜索
ret, ans = [], []
def dfs(i):
if i == len(s):
ret.append(ans[:])
return
for j in range(i, len(s)):
if dp1[i][j]:
ans.append(s[i:j+1])
dfs(j+1)
ans.pop()
dfs(0)
return ret
方法二的记忆化搜索预处理更为强大,由于它和DP是反向的,正常情况不考虑,如果出现i >= j的情况,那一定是剩一个元素或者是两个元素相同的情况,在s[i]和s[j]相同的情况下,是一定满足的。DP不能用是因为他是从内到外,根本不会出现这样的情况。
class Solution:
def partition(self, s: str) -> List[List[str]]:
# 记忆化搜索预处理
@cache
def dp(i, j):
if i >= j: return 1
if s[i] == s[j]: return dp(i+1, j-1)
else: return 0
# dfs记忆化搜索
ret, ans = [], []
def dfs(i):
if i == len(s):
ret.append(ans[:])
return
for j in range(i, len(s)):
if dp(i, j):
ans.append(s[i:j+1])
dfs(j+1)
ans.pop()
dfs(0)
return ret
其实前两种方法的预处理还是弱了点,咱们可以用马拉车实现O(N)的预处理。
class Solution:
def partition(self, s: str) -> List[List[str]]:
# 马拉车处理
if len(s) < 1: return s
ss = "#"
for c in s:
ss += c + '#'
maxRight, center = 0, 0
dp = [0 for _ in ss]
def expand(i, j, maxRight, center):
while i+j < len(ss) and ss[i+j] == ss[i-j]:
j += 1
if i+j-1 > maxRight:
maxRight = i+j-1
center = i
dp[i] = j-1
return maxRight, center
for i, c in enumerate(ss):
mirror = 2*center - i
if i >= maxRight or mirror < 0:
maxRight, center = expand(i, 1, maxRight, center)
elif dp[mirror] < maxRight-i:
dp[i] = dp[mirror]
elif dp[mirror] > maxRight-i:
dp[i] = maxRight-i
else:
maxRight, center = expand(i, dp[mirror]+1, maxRight, center)
def isPara(i, j):
return dp[i+j+1] >= j-i
# dfs回溯
ret, ans = [], []
def dfs(i):
if i == len(s):
ret.append(ans[:])
return
for j in range(i, len(s)):
if isPara(i, j):
ans.append(s[i:j+1])
dfs(j+1)
ans.pop()
dfs(0)
return ret