leetcode 5 最长回文子串 动态规划 python深浅复制
知识准备——动态规划
四大部分
- 划分状态,即将父问题划分为可以递归的若干相同模式的子问题。
- 状态表示,即将子问题用计算机可以理解的逻辑符号(函数等)或程序代码表示。
- 状态转移,即确定父问题和子问题之间的关系,列出状态转移方程。
- 确定边界,即确定最小的子问题、最大的父问题、初始状态、最终状态。
经典模型
- 线性模型(斐波那契数列问题)
- 区间模型(最大回文串问题)
- 树状模型(公司优秀员工问题)
解法
- 自顶向下(记忆化搜索),即从最终状态出发,若子问题已经有解,那么直接拿来用,若子问题无解,那么转化为求解子问题,一般采用递归方式求解。适合树/图状结构的问题。
- 自底向上,即从初始状态出发,根据已经确定的拓扑序一步步求解子问题,直到达到最终状态。适合线性模型等可以简单确定拓扑序的问题。
题目分析
在本题中,划分状态为s中从i位置到j位置的子串是否为回文串;状态表示为布尔值数组dp[i][j];状态转移方程为dp[i][j]=dp[i+1][j-1] and (s[i]==s[j]);确定边界为若i==j,dp[i][j]=True,若i+1==j and s[i]==s[j],dp[i][j]=True;解法为自底向上,按照长度从小到大枚举所有的子串,某长度的子串是建立在长度小于它的子串的判断结果上的。
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
res = ""
max = 0
dp = [[False]*n for _ in range(n)]
for l in range(len(s)):
for i in range(len(s)):
j=i+l
if j>=len(s):
break
if i==j:
dp[i][j]=True
elif i==j-1:
dp[i][j]=(s[i]==s[j])
else:
dp[i][j]=(dp[i+1][j-1] and s[i]==s[j])
if dp[i][j] and j-i+1>max:
max = j-i+1
res = s[i:j+1]
return res
6464ms 24.1MB
深复制与浅复制
在创建n*n的数组dp时,首先我的写法是
dp = [[False]*len(s)]*len(s)
但是这种写法是会报错的,猜测可能是python中的安全性保护措施,改为
n = len(s)
dp = [[False]*n]*n
即不报错,但是这种写法仍然是有问题的
python中的内存和变量的关系有深复制和浅复制之分,对于元组
a = [1,2,[3,4]]
假如通过浅复制将a复制到b中,那么对于首层的元素,他们的内存地址是分别独立的,即更改b[0],b[1],b[2]的值,a[0],a[1],a[2]的值不会随之改变,但是更改b[2][0],b[2][1]的值,a[2][0],a[2][1]的值则会随之改变,即首层以下的元素,他们是同一内存地址,互为引用
假如通过深复制,那么对于任意一层的元素,他们的内存地址都是分别独立的
因此这种写法会导致dp数组实际上每一行的内存地址是相同的,*是一种浅复制
解决措施:不使用*号,这将导致浅复制,乖乖使用枚举所有元素的方法初始化元组,这里可以使用 for _ 语句
n = len(s)
dp = [[False for _ in range(n)] for _ in range(n)]
当然由于只有两层,也可以写成
n = len(s)
dp = [[False]*n for _ in range(n)]
另外也可以使用numpy进行初始化,leetcode也支持
n = len(s)
a = np.zeros(shape=(n,n),dtype=bool)