这个通配符匹配算法的时间复杂度是多少?
Posted
技术标签:
【中文标题】这个通配符匹配算法的时间复杂度是多少?【英文标题】:What's time complexity of this algorithm for Wildcard Matching? 【发布时间】:2014-10-29 14:04:00 【问题描述】:通配符匹配实现通配符模式匹配,支持“?”和“*”。
'?'匹配任何单个字符。 '*' 匹配任意字符序列(包括空序列)。匹配应覆盖整个输入字符串(不是部分)。
函数原型应该是: bool isMatch(const char *s, const char *p)
一些例子:
isMatch("aa","a") → 假 isMatch("aa","aa") → 真 isMatch("aaa","aa") → 假 isMatch("aa", "*") → 真 isMatch("aa", "a*") → 真 isMatch("ab", "?*") → true isMatch("aab", "c*a*b") → 假
问题:
什么是时间复杂度? 什么是空间复杂度?我个人认为
时间复杂度高度依赖“输入”,不能写 像 T = O(?)。 空间复杂度 = O(min(sLen, pLen)),因为最大递归 深度 = O(min(sLen, pLen))。试过了: 写出时间复杂度表达式,然后绘制递归树:
TC Expression => T(n) = T(n - 1) + O(1), when pChar == '?' or pChar == sChar,
= T(n - 1) + T(n - 1) + O(1), when pChar == '*'.
我尝试绘制递归树,但不知道如何根据这种时间复杂度表达式绘制它。
其他问题: 准确地说,我希望知道如何计算这种递归的时间复杂度,它基于输入有多个不可预见的分支。
注意:
我知道迭代解决方案和递归解决方案,但不能 弄清楚如何计算时间复杂度 递归解决方案。 这不是作业,这个问题来自“leetcode.com”,我 只是希望知道如何计算时间复杂度的方法 这种特殊的递归。代码: Java,解决方案: 递归。
public class Solution
public boolean isMatch(String s, String p)
// Input checking.
if (s == null || p == null) return false;
int sLen = s.length();
int pLen = p.length();
return helper(s, 0, sLen, p, 0, pLen);
private boolean helper(String s, int sIndex, int sLen,
String p, int pIndex, int pLen)
// Base case.
if (sIndex >= sLen && pIndex >= pLen) return true;
else if (sIndex >= sLen)
// Check whether the remaining part of p all "*".
while (pIndex < pLen)
if (p.charAt(pIndex) != '*') return false;
pIndex ++;
return true;
else if (pIndex >= pLen)
return false;
char sc = s.charAt(sIndex);
char pc = p.charAt(pIndex);
if (pc == '?' || pc == sc)
return helper(s, sIndex + 1, sLen, p, pIndex + 1, pLen);
else if (pc == '*')
return helper(s, sIndex, sLen, p, pIndex + 1, pLen) ||
helper(s, sIndex + 1, sLen, p, pIndex, pLen);
else return false;
【问题讨论】:
我觉得你的问题没问题。不过,我将编辑一些关于否决票的噪音;它们发生了,不一定需要解释(尤其是如果对方不想给你解释)。 你是对的,谢谢@Makoto 请注意 - 如果您使用带有记忆的递归(本质上是自上而下的 DP),这应该非常快,因为有很多重叠的子问题。 【参考方案1】:为了获得最坏情况运行时间的上限(即大 O),您需要假设最坏情况。将长度为s
的字符串与长度为p
的模式匹配的渐近运行时间上界的正确递归如下。
T(s, p) | s == 0 || p == 0 = 1
| s > 0 && p > 0 = 1 + max(T(s, p - 1) + T(s - 1, p), // *
T(s - 1, p - 1)) // ? or literal
解决像这样的二变量递归可能很棘手。在这种特殊情况下,可以很容易地通过归纳证明T
在两个参数中都没有递减,因此我们可以简化最大值。
T(s, p) | s == 0 || p == 0 = 1
| s > 0 && p > 0 = 1 + T(s, p - 1) + T(s - 1, p)
现在,有经验的人可以认识到binomial coefficients 与重复出现的强烈相似之处,并进行(诚然有点神奇)替换s = n - k
和p = k
和T(s, p) = 2 U(n, k) - 1
。
2 U(n, k) - 1 | n == k || k == 0 = 1
| n > k && k > 0 = 1 + 2 U(n - 1, k - 1) - 1 + 2 U(n - 1, k) - 1
U(n, k) | n == k || k == 0 = 1
| n > k && k > 0 = U(n - 1, k - 1) + U(n - 1, k)
我们通过斯特林的近似得出T(s, p) = 2 U(s + p, p) - 1 = 2 ((s + p) choose p) - 1 = O(2^(s + p)/sqrt(s + p))
的结论(这是单个数量s + p
中可能的最佳大O 界,但如果我写大Theta 会令人困惑)。
到目前为止,我们只证明了T(s, p)
是一个上限。由于*
是更麻烦的情况,因此出现了最坏情况的想法:将模式全部设为*
s。我们必须要小心一点,因为如果匹配成功,那么就有可能发生短路。然而,阻止匹配只需要很少的时间:考虑字符串0000000000
和模式**********1
(根据需要调整0
s 和*
的数量)。这个例子表明,引用的界限紧在一个多项式因子内(可以忽略不计,因为运行时间已经是指数级的)。
为了获得一个上限,没有必要如此精确地计算出这些递归。例如,我可能会猜到 T(s, p) <= 3^(s + p)
并继续通过归纳验证该声明。
T(s, p) | s = 0 || p = 0 = 1 <= 3^(s + p) // base case
| s > 0 || p > 0 = 1 + T(s, p - 1) + T(s - 1, p) // induction
<= 3^(s + p - 1) + 3^(s + p - 1) + 3^(s + p - 1)
= 3^(s + p)
现在,3^(s + p)
是一个有效的上限,但鉴于此答案的其余部分,它并不严格。现在可以在边界内寻找浪费;例如,1 <= 3^(s + p - 1)
是一个严重的高估,通过一些技巧,我们可以得到指数基数 2
。
然而,更重要的业务顺序是获得指数下限。通过为上面的坏例子绘制递归树,我可以推测T(s, p) >= 2^min(s, p)
。这可以通过归纳来验证。
T(s, p) | s = 0 || p = 0 = 1 >= 2^min(s, p) = 2^0 = 1 // base case
| s > 0 && p > 0 = 1 + T(s, p - 1) + T(s - 1, p) // induction
>= 2^min(s, p - 1) + 2^min(s - 1, p)
>= 2^(min(s, p) - 1) + 2^(min(s, p) - 1)
= 2^min(s, p)
【讨论】:
非常感谢@DavidEisenstat,我需要一些时间来深入思考你的答案,计算时间复杂度的“替代”真的很熟练,对我来说有点难。 @Zhaonan 是的,我对答案的那部分感到有些沮丧。就像在微积分中做积分一样,这只是需要时间培养的技能之一。我研究它是因为我发现组合数学很漂亮,但在我作为一名从事算法研究的博士生的职业生涯中,它几乎没有什么用处。 @Zhaonan 让我编辑一下如果我没有或无法识别一个众所周知的复发我会做什么。 非常感谢,你很专业!我真的很高兴从你那里得到答案:)以上是关于这个通配符匹配算法的时间复杂度是多少?的主要内容,如果未能解决你的问题,请参考以下文章