2021-08-10 字符串:子串问题——关于循环,递归,迭代,动归,记忆化,背包,减枝...

32 阅读4分钟

2021-08-10 字符串:子串问题——关于循环,递归,迭代,动归,记忆化,背包,减枝...

理解与分析

首先介绍一下这四者的区别和联系:剑指offer2 类型总结:循环,递归,迭代和动态规划
递推和回溯:是递归的两个阶段!然后记忆化,剪枝都是为了更快(或更低复杂度)完成的操作

单词拆分

  • dfs V1.0(这一版会超出时间限制,所以我们需要+记忆化):
class Solution:
    flag = False
    def wordBreak(self, s: str, wordDict) -> bool:
        @cache
        def dfs(s: str) -> bool:
            global flag
            # 空字符串不是None,None是空字符串
            if s == "":
                flag = True
            else:
                flag = False

            # 注意range和字符串边界问题
            for _ in range(len(s)):
                if s[0:_+1] in wordDict:
                    dfs(s[_+1:])
                if flag:
                    break
            # 注意递归的特性,此处才是最终返回的,也就是只有最外层的才会被返回,所以一般是在递归函数之外定义global(或成员)变量/直接使用全局变量如列表等
            return flag
        
        return dfs(s)
  • dfs V2.0:

    • python简直是黑科技…其实只需要在dfs上面加上一个@cache,相当于机器帮你完成了记忆化的操作,但其实这个记忆化非常简单,是对函数的参数和结果进行了一个存储。瞬间从超时变成了击败97%的用户,第一次用感觉太神奇了,记录一下:
      在这里插入图片描述
    • 下面这是正儿八经的记忆化操作:还是先找状态转移方程,如果第一遍分割的dfs未通过,在进行第二遍分割时候,s的前半部分在第一次判断过了,那再次遍历的时候只需要在维护的列表中取出来结果+判断后面的就好啦,所以true if s[0:_] and s[_:]
    # debug不出来了,天啊😭还是超时,好像没起到记忆化的效果
    class Solution:
        flag = False
    
        def wordBreak(self, s: str, wordDict) -> bool:
            memo = [False for _ in range(len(s))]
    
            def dfs(start: int, s: str) -> bool:
                global flag
                # 空字符串不是None,None是空字符串
                if s == "":
                    flag = True
                else:
                    flag = False
    
                # 注意range和字符串边界问题
                for _ in range(len(s)):
                    print(start+_, memo)
                    if memo[start+_] or s[0:_+1] in wordDict:
                        memo[start+_] = True
                        dfs(start+_+1, s[_+1:])
                    if flag:
                        memo[start+_] = True
                        break
    
                return flag
    
            return dfs(0, s)
    
  • 动态规划:所有的递归的返回过程实际上就是动态规划!!!让我们从开始条件(逐步分解至最小的情况即第一个词就是单词)记录,直到最终填充完所有的memo(下面代码中使用的是dp),这是力扣一个解答中的代码,包括上面的dfs也有更好的写法,太优雅啦:

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:       
        n=len(s)
        dp=[False]*(n+1)
        dp[0]=True
        for i in range(n):
            for j in range(i+1,n+1):
                if(dp[i] and (s[i:j] in wordDict)):
                    dp[j]=True
        return dp[-1]

单词拆分Ⅱ

  • dfs+回溯过程中的记忆化(防止超时),将list转为Set(哈希存取),能进一步节省资源:
class Solution:
    # 带备忘录的记忆化搜索,使用@cache是一个道理
    def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
        res = []
        memo = [1] * (len(s)+1)
        wordDict = set(wordDict)

        def dfs(wordDict, ans, pos):
        	# 回溯前先记下答案中有多少个元素
            num = len(res)                  
            if pos == len(s):
                res.append(" ".join(ans))
                return
            for i in range(pos, len(s)+1):
                if memo[i] and s[pos:i] in wordDict:  
                    ans.append(s[pos:i])
                    dfs(wordDict, temp, i)
                    temp.pop()
            # 答案中的元素没有增加,说明s[pos:]不能分割,修改备忘录
            memo[pos] = 1 if len(res) > num else 0

        dfs(wordDict, [], 0)
        return res
  • 先动态规划判断是否能(动态规划的利弊就看出来啦,因为是串,用一个数组记录,将二维->一维,同时不再有记录路径过程,主要是为了防止 不能分割的长字符串的不断递归 ),再获得路径:
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
        n = len(s)
        # 1.动态规划判断单词是否可拆分,其实这里就是针对那个"aaaaaaaaa"的情况,让它不要通过Dfs的算法去递归遍历了
        dp  = [False]*(n+1)
        dp[0] = True
        for i in range(n):
            for j in range(i+1, n+1):
                if s[i:j] in wordDict and dp[i]:
                    dp[j] = True
        if not dp[-1]:
            return []
            
        # 2.对于可拆分的数组,使用DFS求得所有拆分组合:即简化问题为“给你一个可拆分的数组,将可拆分的组合返回”,就很经典了
        result = []
        def dfs(string, ans):
            if len(string)<=0:
                result.append(" ".join(ans[:]))
                return 
            for i in range(0, len(string)+1):
                if string[0:i] in wordDict:
                    ans.append(string[0:i])
                    dfs(string[i:], ans)
                    ans.pop()
                    
        dfs(s, [])
        return result
  • 根据题意,因为worddict个数是有限的,所以直接根据它的长度来分:
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
        def dfs(count):
            if count == 0:
                res.append(' '.join(lst))
                return
            for word in wordDict:
                size = len(word)
                if size <= count and s[N-count:N-count+size] == word:
                    lst.append(word)
                    dfs(count - size)
                    lst.pop()
        
        res = []
        lst = []
        N = len(s)
        dfs(N)
        return res

单词搜索