遞歸和帶記憶的遞歸(原理和例子)

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 = [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)來儲存計算結果,會發現計算次數明顯減少。

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記錄了各個自字符串的匹配情況,這樣的話就不需要每個都遞歸到底了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章