Leetcode上看到这么一句话
dynamic programming is basically recursion with caching
这和之前的一些做题经历比较契合。
dp,重要的是找递推关系 dp[i] = f(dp[i-1]) 或者 dp[i][j] = f(dp[i-1][j], dp[i][j-1])等等
但是有时候关系没有那么好找到
也有的时候不会第一时间意识到这是dp问题
我发现,dp往往会涉及最大(最小)值的问题。适合的就是记截止到第i步的最值为dp[i]
当然有时候更容易想到递归,但是递归会涉及到很多的重复计算,往往会超时。如果能够将一些递归的结果存储起来,避免重复计算,会减少不少用时,但还是会涉及层层函数调用,
依旧没有dp来的那么划算。尽管如此,还是可以从递归入手,去启发找到dp的递推关系。可能自上而下比自上而下好想一点吧
另外就是要关注递归的结束条件,也就是dp数组的初始化行&列,有时会比较特殊。
例子:leetcode72 计算两字符串的edit distance
递归思路算是最好想的
class Solution:
def minDistance(self, word1, word2):
"""Naive recursive solution"""
if not word1 and not word2:
return 0
if not word1:
return len(word2)
if not word2:
return len(word1)
if word1[0] == word2[0]:
return self.minDistance(word1[1:], word2[1:])
insert = 1 + self.minDistance(word1, word2[1:])
delete = 1 + self.minDistance(word1[1:], word2)
replace = 1 + self.minDistance(word1[1:], word2[1:])
return min(insert, replace, delete)
有字典存储结果避免重复计算的
class Solution:
def minDistance(self, word1, word2, i, j, memo):
"""Memoized solution"""
if i == len(word1) and j == len(word2):
return 0
if i == len(word1):
return len(word2) - j
if j == len(word2):
return len(word1) - i
if (i, j) not in memo:
if word1[i] == word2[j]:
ans = self.minDistance2(word1, word2, i + 1, j + 1, memo)
else:
insert = 1 + self.minDistance2(word1, word2, i, j + 1, memo)
delete = 1 + self.minDistance2(word1, word2, i + 1, j, memo)
replace = 1 + self.minDistance2(word1, word2, i + 1, j + 1, memo)
ans = min(insert, delete, replace)
memo[(i, j)] = ans
return memo[(i, j)]
自下而上dp就很简单了
class Solution:
def minDistance(self, word1, word2):
"""Dynamic programming solution"""
m = len(word1)
n = len(word2)
table = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
table[i][0] = i
for j in range(n + 1):
table[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
table[i][j] = table[i - 1][j - 1]
else:
table[i][j] = 1 + min(table[i - 1][j], table[i][j - 1], table[i - 1][j - 1])
return table[-1][-1]