递归和带记忆的递归(原理和例子)

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记录了各个自字符串的匹配情况,这样的话就不需要每个都递归到底了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

comli_cn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值