0. 递归和带记忆的递归
当原问题和它的子问题的形式以及解题思路一样的时候就可以使用递归来解决,但是递归会发生重复计算的问题,如果递归的深度过深的话会使计算时间大大增加,所以引入了带记忆的递归。带记忆的递归会记住已经计算过的递归路径,当程序再次访问这个路径的时候会直接获得返回值而不需要一层层往下计算。
1.斐波那契数列
斐波那契数列指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
普通的递归解法:
def fibonacci(n):
print(n)
if n == 1 or n == 2:
return 1
else:
return fibonacci(n-1)+fibonacci(n-2)
print("result=", fibonacci(7))
结果为:13
7
6
5
4
3
2
1
2
3
2
1
4
3
2
1
2
5
4
3
2
1
2
3
2
1
result= 13
每次打印出来的n代表了对fibonacci(n)的计算,会发现有重复计算的。
计算过程如下图:
可以发现在计算过程中会有重复计算,比如对
f
(
4
)
f(4)
f(4)的计算就要进行两次,但是如果在进行第一次
f
(
4
)
f(4)
f(4)的计算时就将其结果记录下来,那么第二次就不用一步步计算
f
(
4
)
f(4)
f(4)了,直接返回答案即可,这相当于一个剪枝的过程。这就是带记忆的递归的基本思想。
带记忆的递归解法:
f = [0]*1000
def fibonacci(n):
print(n)
if n == 1 or n == 2:
f[n]=1
return 1
else:
if f[n] == 0:
f[n] = fibonacci(n-1)+fibonacci(n-2)
return f[n]
else:
return f[n]
print("result=", fibonacci(7))
结果为:13
7
6
5
4
3
2
1
2
3
4
5
result= 13
这里用 f ( n ) f(n) f(n)来储存计算结果,会发现计算次数明显减少。
2. 通配符匹配(LeetCode)
Leetcode上算法第44题也就是通配符匹配也可以用递归或带记忆的递归来解决,带记忆的递归显然更好一些。
题目描述:
给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。
‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
递归解题:
当‘*’单独出现的时候:
case1:如果字符串相等p == s,返回 True。
case2:如果 p == ‘*’,返回 True。
case3:如果 p 为空或 s 为空,返回 False。
case4:若当前字符匹配,即 p[0] == s[0] 或 p[0] == ‘?’,然后比较下一个字符,返回 isMatch(s[1:], p[1:])。
case5:如果当前的字符模式是一个星号 p[0] == ‘*’,则有两种情况。
----------(1) 星号没有匹配字符,因此答案是 isMatch(s, p[1:])。
----------(2) 星号匹配一个字符或更多字符,因此答案是 isMatch(s[1:], p)。
case6:若 p[0] != s[0],返回 False。
按照上面的分析就可以写出如下程序:
class Solution:
def remove_duplicate_stars(self, p):
if p == '':
return p
pl = [p[0]]
for x in p[1:]:
if pl[-1] != '*' or pl[-1] == '*' and x != '*':
pl.append(x)
return ''.join(pl)
def isMatch(self, s, p):
p = self.remove_duplicate_stars(p)
if s == p:
return True
elif p == '*':
return True
elif p == '' or s=='':
return False
elif s[0] == p[0] or p[0] == '?':
return self.isMatch(s[1:], p[1:])
elif p[0] == '*':
return self.isMatch(s, p[1:]) or self.isMatch(s[1:], p)
else:
return False
solution = Solution()
s = "acdcb"
p = "a*c?b"
result = solution.isMatch(s, p)
print(result)
结果是:False
其中remove_duplicate_stars(self, p):是为了对p中的‘*’进行去重处理。
带记忆的递归解题:
与求斐波那契数列时相同,这个问题的递归也会对子字符串是否匹配进行重复判断,所以要加一个能对子字符串匹配结果进行记录的容器,这里选择字典:
class Solution:
def remove_duplicate_stars(self, p):
if p == '':
return p
p1 = [p[0],]
for x in p[1:]:
if p1[-1] != '*' or p1[-1] == '*' and x != '*':
p1.append(x)
return ''.join(p1)
def helper(self, s, p):
dp = self.dp
if (s, p) in dp:
return dp[(s, p)]
if p == s or p == '*':
dp[(s, p)] = True
elif p == '' or s == '':
dp[(s, p)] = False
elif p[0] == s[0] or p[0] == '?':
dp[(s, p)] = self.helper(s[1:], p[1:])
elif p[0] == '*':
dp[(s, p)] = self.helper(s, p[1:]) or self.helper(s[1:], p)
else:
dp[(s, p)] = False
return dp[(s, p)]
def isMatch(self, s, p):
p = self.remove_duplicate_stars(p)
# memorization hashmap to be used during the recursion
self.dp = {}
return self.helper(s, p)
字典dp记录了各个自字符串的匹配情况,这样的话就不需要每个都递归到底了。