给定一个字符串 str,能否从字符串中移除部分(0个或多个)字符使其变为回文串,此处空串认为是回文串,求多少移除方案。(注意:相同字符的由于的移除,认为不同的移除方案)。
【例如】str = “XXY” 有 4 中移除方案
方案一:暴力规划
移除字符与保留字符效果是一样的。
可能性尝试:范围尝试模型。
f( i , j ) 定义:str[ i: j+1 ] 子串中包含回文个数。最终结果:f ( 0, len(str) - 1 )
可能性分类:
开头 | 结尾 | |
---|---|---|
① | 以 i 开头 | 以 j 结尾 |
② | 不以 i 开头 | 以 j 结尾 |
③ | 不以 i 开头 | 不以 j 结尾 |
④ | 以 i 开头 | 不以 j 结尾 |
以上所有分类:互斥。最终的解是所有可能性的全集:求和。
要求 f (i,j) 解,必须先得到 f( i, j) 最近的子问题
f ( i, j-1 ) 是 str[ i: j ]子串中包含回文个数,不包含字符 str[j] ,所以以 j 结尾的可能性都不可能。
f ( i, j-1 ) = ③ + ④
同理:
f ( i+1, j ) = ② + ③
f ( i+1, j-1 ) = ③
现在缺少可能性 ①
可能性① 要求:以 i 开头且以 j 结尾。如果str[i] != str[j],不可能出现可能性 ①
此时结果:f ( i, j ) = f ( i, j-1 ) + f ( i+1, j ) - f ( i+1, j-1 ) = (③ + ④)+ ( ② + ③)- ③ = ② + ③ + ④
如果str[i] == str[j]:不可能出现可能性 ③,③ = 0
f ( i, j ) = f ( i, j-1 ) + f ( i+1, j ) + 1 = ② + ④ + 1
1 是 str[i]str[j] 这个回文串
def min_remove(string):
if not string: return 0
return f(string, 0, len(string) - 1)
def f(string, i, j):
if i == j: return 1
if i + 1 == j: return 3 if string[i] == string[j] else 2
res = f(string, i, j - 1) + f(string, i + 1, j)
if string[i] == string[j]:
res += 1
else:
res -= f(string, i + 1, j - 1)
return res
方案二:动态规划
def min_remove2(string):
if not string: return 0
n = len(string)
dp = [[0] * n for _ in range(n)]
dp[-1][-1] = 1
for i in range(n - 1):
dp[i][i] = 1
dp[i][i + 1] = 3 if string[i] == string[i + 1] else 2
for row in range(n - 3, -1, -1):
for col in range(row + 2, n):
dp[row][col] = dp[row][col - 1] + dp[row + 1][col]
if string[row] == string[col]:
dp[row][col] += 1
else:
dp[row][col] -= dp[row + 1][col - 1]
return dp[0][-1]
方案三:动态规划–滚动数组
def min_remove3(string):
if not string: return 0
if len(string) == 1: return 1
n = len(string)
dp = [1] * n
dp[-1] = 3 if string[-1] == string[-2] else 2
for row in range(n - 3, -1, -1):
tmp = 1
dp[row + 1] = 3 if string[row + 1] == string[row] else 2
for col in range(row + 2, n):
new_value = dp[col - 1] + dp[col]
if string[row] == string[col]:
new_value += 1
else:
new_value -= tmp
tmp = dp[col]
dp[col] = new_value
return dp[-1]
对数器
import random
def generator_random_str(max_size):
alphabet = [chr(i) for i in range(97, 105)]
size = int(random.random() * max_size)
return ''.join([random.sample(alphabet, 1)[0] for _ in range(size)])
def check():
max_size = 10
for i in range(500):
stirng1 = generator_random_str(max_size)
res1 = min_remove(stirng1)
res2 = min_remove2(stirng1)
res3 = min_remove3(stirng1)
if res1 != res2 or res1 != res3:
print("ERROR", stirng1, "res1=", res1, "res2=", res2, "res3=", res3)
print("OVER")
check()
总结:本题考察是范围尝试模型,难点在于可能性分类后,要组合出最终的答案。