leetcode - 467. Unique Substrings in Wraparound String

算法系列博客之Dynamic Programming

本篇博客将运用动态规划的思想来解决leetcode上467号问题


问题描述:

Consider the string s to be the infinite wraparound string of “abcdefghijklmnopqrstuvwxyz”, so s will look like this: “…zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd….”.

Now we have another string p. Your job is to find out how many unique non-empty substrings of p are present in s. In particular, your input is the string p and you need to output the number of different non-empty substrings of p in the string s.

Note: p consists of only lowercase English letters and the size of p might be over 10000.

这里先对题目的关键要求简要说明如下:
      ·   目的是计算字符串p中满足特定需求的子串的个数
      ·   要求是能够在字符串s中找到相同的子串与之字面值完全相同
      ·   相同子串不做重复计算,且字面值相同的子串均视为相同子串

首先,观察字符串s,假定 ‘z’和 ‘a’视作连续,那么字符串s的任意子串均是字典序连续的,也即是说p中满足需求的子串也一定是连续的,这是一个非常关键的入手点。
其次,对于字符串增加一个字符,那么它所增加的子串,均是以该字符结尾向前延续的
因而如果我们做出假设
  res[i]表示从0到i的p的子串所对应的满足需求的子串数目
  continue[i]表示从i向前最长连续的个数
那么res[i] = res[i-1] + continue[i]
回顾之前的要求,发现它只满足了前两点,并不满足第三点

考虑一个子串可能的情况,前面的推理过程就表达出了一种区分子串的方法:
    根据结尾字符和长度来对子串进行区分
而且一旦结尾字符为c长度为l的子串出现,那么结尾为c长度为1-l的子串均已经出现并进行计算了,因而可以得到下面一种方法来防止重复计算
数组f[c]表示以c结尾目前已经出现的最长的连续子串的长度
至此,再对前面的状态转移方程进行修改
    res[i] = res[i-1] + (continue[i] > f[p[i]] ? continue[i] - f[p[i]] : 0 )
同时需要对f进行更新
    f[p[i]] = max(continue[i], f[p[i]])

最后考虑其初始状态,如果p不为空串以0作为起始位数,i = 0时:
    res[0] = 1; f[p[0]] = 1
    数组f长度26, 其余元素均为0

此时,可以着手开始用代码实现:

class Solution(object):
    def findSubstringInWraproundString(self, p):
        if not p: return 0
        res = [0] * len(p)
        res[0] = 1
        continue_num = [0] * len(p)
        f = [0] * 26
        f[ord(p[0]) - ord('a')] = 1
        for i in range(1, len(p)):
            if ord(p[i]) == ord(p[i-1]) + 1 or (p[i] == 'a' and p[i-1] == 'z'):
                continue_num[i] = continue_num[i-1] + 1
            else: continue_num[i] = 1

            if continue_num[i] > f[ord(p[i]) - ord('a')]:
                res[i] = res[i-1] + continue_num[i] - f[ord(p[i]) - ord('a')]
                f[ord(p[i]) - ord('a')] = continue_num[i]
            else: res[i] = res[i-1]
        return res[len(p)-1]

经过测试,运算结果正确无误;但再仔细回顾整个推理过程,发现res和continue仅仅只是依赖于上一次的计算结果,因而完全可以简化这里的空间复杂度,得到如下的实现方式

class Solution(object):
    def findSubstringInWraproundString(self, p):
        if not p: return 0
        res = continue_num = 1
        f = [0] * 26
        f[ord(p[0]) - ord('a')] = 1
        for i in range(1, len(p)):
            if ord(p[i]) == ord(p[i-1]) + 1 or (p[i] == 'a' and p[i-1] == 'z'):
                continue_num += 1
            else: continue_num = 1

            if continue_num > f[ord(p[i]) - ord('a')]:
                res += continue_num - f[ord(p[i]) - ord('a')]
                f[ord(p[i]) - ord('a')] = continue_num
        return res

比较起来,代空间复杂度降低的同时还精简了代码,使得逻辑更加清晰可读

复杂度分析,假设p的长度为n
时间上,仅仅只有一层循环,循环内无嵌套,常数指令数,因而O(n)
空间上,所用空间为常数,不依赖于任何变量,因而O(1)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值