LeetCode115:不同的子序列 [Python3实现]

突然看见有个高手关注我,感觉非常兴奋,然后去看了下他的博客,里面有篇 LeetCode115:不同的子序列 [Python3实现]。

仔细看了看他的算法,再搜索了一下其他人的算法,发现都是在双重循环的时候直接循环到底的,但是我仔细考虑了一下,觉得似乎还可以再进一步优化一下,这样在大数据量的时候,就算节约10%的时间那也是很有数算的。

下面把其他人的算法和我进一步优化的算法都写在下面,请各位指教。

题目:

给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
示例 1:
输入: S = “rabbbit”, T = “rabbit”
输出: 3
解释:
如下图所示, 有 3 种可以从 S 中得到 “rabbit” 的方案。
(上箭头符号 ^ 表示选取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^

普通算法:

def seekStr2(mainStr, subStr):
    subLen = len(subStr)
    mainLen = len(mainStr)
    num = [[0 for _ in range(mainLen)] for _ in range(subLen)]
    if subStr[0] == mainStr[0]:
        num[0][0] = 1
    for i in range(1, mainLen):
        if mainStr[i] == subStr[0]:
            num[0][i] = num[0][i-1] + 1
        else:
            num[0][i] = num[0][i-1]
    for i in range(1, subLen):
        for j in range(i, mainLen):
            if mainStr[j] == subStr[i]:
                num[i][j] = num[i][j-1] + num[i - 1][j-1]
            else:
                num[i][j] = num[i][j-1]
    number = num[subLen-1][mainLen-1]
    return number

我的算法:

我仔细列表计算以后发现,其实真正使用到的是二维表中有限的数据,即以子串为行,母串为列建立的阵列中,子串的每个字母都只有在一个区间中的出现次数是有效的,超出这个区间的数都是多余的,因此计算的时候其实只需要循环该字母的有效区间即可,大于该区间以后,无论后面有没有这个字母了,其计算次数都是以有效区间的最后一次的次数为准。

所以我先用一次单循环求出每个字母的有效区间,然后在该区间循环并记录尾数以备下一个字母使用。

class Solution(object):
    def numDistinct(self, mainStr, subStr):
        """
        :type mainStr: str
        :type subStr: str
        :rtype: int
        """
        if (mainStr == "" or subStr == ""):
            return 0
        subLen = len(subStr)
        mainLen = len(mainStr)
        if (mainLen < subLen):
            return 0
        endIndex = self.getEndIndex(mainStr, subStr)
        if endIndex == []:
            return 0
        endNum = 0
        num = [[0 for _ in range(mainLen)] for _ in range(subLen)]
        if subStr[0] == mainStr[0]:
            num[0][0] = 1
            endNum = 1
        for i in range(1, endIndex[0]):
            if mainStr[i] == subStr[0]:
                num[0][i] = num[0][i - 1] + 1
            else:
                num[0][i] = num[0][i - 1]
            endNum = num[0][i]
        n = 0
        for i in range(1, subLen):
            for j in range(i, endIndex[i]):
                if mainStr[j] == subStr[i]:
                    if j > endIndex[i - 1]:
                        num[i][j] = num[i][j - 1] + endNum
                    else:
                        num[i][j] = num[i][j - 1] + num[i - 1][j - 1]
                else:
                    num[i][j] = num[i][j - 1]
                n = num[i][j]
            endNum = n
        return endNum

    def getEndIndex(self, mainStr, subStr):
        subLen = len(subStr)
        mainLen = len(mainStr)
        k = 1
        endIndex = []
        for i in range(mainLen - 1, -1, -1):
            if subStr[subLen - k] == mainStr[i]:
                endIndex.append(i + 1)
                k += 1
                if k > subLen:
                    break
            if (subLen - k > i or i == 0):
                endIndex = []
                break
        endIndex = endIndex[::-1]
        return endIndex

这样看起来似乎比普通的算法更复杂,计算步骤更多,但是并不会多花多少时间,而且在某些情况下效率可以提高30%左右,特殊情况甚至更多。

mainStr = f"{'r'*1000}{'a'*1000}{'b'*1000}{'i'*1000}{'t'*1000}{'s'*1000}{'r'*1000}{'a'*1000}{'b'*1000}{'i'*1000}{'t'*1000}"
subStr = f"rabbits"
start = time.time()
result = seekStr2(mainStr, subStr)
end = time.time()
#for l in result:
#    print(l)
print(f"共有{result}种组合,花时:{end - start}")

start = time.time()
result = seekStr3(mainStr, subStr)
end = time.time()
print(f"共有{result}种组合,花时:{end - start}")



共有499500000000000000000种组合,花时:0.012966156005859375
共有499500000000000000000种组合,花时:0.006979942321777344

 今天跑去LeetCode网站看了下,发现人家真正高手的算法好简单,然后仔细分析了人家的算法,总算是明白了。

如图所示,检查母字符串中每个字母ch在子串中最后出现的位置 pos,如果pos>0则子串对应位置sta[pos] += sta[pos - 1],如果pos==0则表示是子串首字符,sta[0] += 1,然后继续在子串中向前查找,直到找不到为止。

代码如下:

class Solution2(object):
    def numDistinct(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: int
        """
        n = len(t)
        sta = [0] * n
        for ch in s:
            pos = n
            while True:
                pos = t.rfind(ch, 0, pos)
                if pos > 0:
                    sta[pos] += sta[pos - 1]
                elif pos == 0:
                    sta[pos] += 1
                else:
                    break
        return sta[n - 1]

比较我自己的算法,其实已经很接近了,可惜行百里者半九十,我把子串的每个字符在母串中确定最后出现的有效位置,却没能想一下反过来试试,果然还是懒了。开始的时候懒一下,虽然在leetcode中的记录比90%的人效率高,,但是比真正高手的效率仍然低了一半还多,事倍功半啊。看到这篇文章的同学切记切记,起初偷懒轻松后面辛苦,起初勤快吃苦后面舒服!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值