LeetCode1143. Longest Common Subsequence

LeetCode 1143

刚看到这个题目,考虑了dp,但是一下没有想到简单的状态转移方法。所以开始考虑其他方法,首先想到的是递归求解,比如 “abcde” 和“ace", 我们可以把这个问题分成两个子问题,最后的“e"在最长公共字串中用到了,还有一种是这个e没有用到。这样我们可以不断的递归减短这个字符串,直到字符串长度为0.
写成递归的代码:

    def longestCommonSubsequenceTimeout(self, text1: str, text2: str) -> int:        
        l1 = len(text1)
        l2 = len(text2)

        if l1 == 0 or l2 == 0: return 0

        last = text2[-1]
        try:
            index = text1.rindex(last)
        except:
        	#特殊case,最后一个字符在第一个字串中不存在,所以只有一种情况
            return self.longestCommonSubsequence(text1, text2[:-1])
            
        #分成用到了最后一个字符和没有用到最后一个字符两种情况
        return max(self.longestCommonSubsequence(text1[0:index], text2[:-1]) + 1, 
                   self.longestCommonSubsequence(text1, text2[:-1]))

这个效率还是比较低的,时间复杂度是指数级的。
所以提交之后time out 了。

那么需要优化,最直接的方法就是用memo,记住访问过的情况:
这个转换也很简单,用字典根据第一个字串的位置和第二个字串做key,记住访问过的情况。

    def longestCommonSubsequenceMemo(self, text1: str, text2: str) -> int:                
        memo = collections.defaultdict(int)
        def longestCommon(text1, text2, memo):
            l1 = len(text1)
            l2 = len(text2)

            if l1 == 0 or l2 == 0: return 0

            if (len(text1), text2) in memo:
                return memo[(len(text1), text2)]

            last = text2[-1]
            try:
                index = text1.rindex(last)
            except:
                memo[(len(text1), text2)] = longestCommon(text1, text2[:-1], memo)
                return memo[(len(text1), text2)]
                
            memo[(len(text1), text2)] = max(longestCommon(text1[0:index], text2[:-1], memo) + 1, 
                    longestCommon(text1, text2[:-1], memo))
            return memo[(len(text1), text2)]

        return longestCommon(text1, text2, memo)

这个被接受了,但是时间只有5%。感觉还是不够好
所以重新回头考虑dp。如果用一个二维数组记住,到位置(i,j) 位置的最长公共字串,那么我们下一个i+1的字符串,只要从j的位置开始match,找到一个k的话,那么(i+1,k)的公共字串就是dp(i,j)+1.
如果找不到k,那么dp【i+1,j】= max(dp【i,j】,dp【i+1,j】)(可能被之前的设置过)
转为代码是:

    def longestCommonSubsequencedp(self, text1: str, text2: str) -> int:        
        l1 = len(text1)
        l2 = len(text2)

        if l1 == 0 or l2 == 0: return 0

        dp = [[-1] * (l1+1) for i in range(l2 + 1)]
        dp[0][0] = 0

        for i in range(1, l2+1):
            for pos, v in enumerate(dp[i-1]):
                if v != -1:
                    dp[i][pos] = max(dp[i][pos], v)
                    try:
                        index = text1.index(text2[i-1], pos)
                    except:                        
                        continue
                    dp[i][index+1] = max(dp[i][index+1], v + 1)
                
        return max(dp[l2])

这个也被接受了,但是时间只beat了10%。每次要index效率还是不高。
到了这步,我们其实记录的dp【i,j】就是到i,j的最长公共字串。那么我么重新考虑dp的状态转移方式。i+1,j+1 的字符不match,那么其实最长字串要么就是(i,j+1)或者是(i+1,j)因为自己是没有match的。如果match了,本身已经被消耗掉了,那么之前最长的只能是(i,j)。转换为代码:

    def longestCommonSubsequence(self, text1: str, text2: str) -> int:        
        l1 = len(text1)
        l2 = len(text2)

        if l1 == 0 or l2 == 0: return 0

        dp = [[0] * (l1+1) for i in range(l2 + 1)]        
		#用了哨兵,省去了特殊处理第一行/列
        for j, t2 in enumerate(text2):
            for i, t1 in enumerate(text1):
                if t1 != t2:
                    dp[j+1][i+1] = max(dp[j+1][i], dp[j][i+1])
                else:
                    dp[j+1][i+1] = dp[j][i] + 1
                
        return dp[l2][l1]

这个效率就还不错了,beat了87%

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值