【问题标题】:Why is the time complexity of this algorithm exponential?为什么这个算法的时间复杂度是指数级的?
【发布时间】:2018-05-12 05:55:51
【问题描述】:

在一次采访中,有人问我以下算法的时间复杂度:

static bool SetContainsString(string searchString, HashSet<string> setOfStrings)
{
    for (int i = 0; i < searchString.Length; i++)
    {
        var segment = searchString.Substring(0, i + 1);

        if (setOfStrings.Contains(segment))
        {
            var remainingSegment = searchString.Substring(segment.Length);

            if (remainingSegment == "") return true;
            return SetContainsString(remainingSegment, setOfStrings);
        }
    }

    return false;
}

我回答“线性”是因为在我看来它只循环通过 searchString 的长度。是的,它是递归的,但是递归调用只是在字符串中还没有被迭代的部分上,所以最终的结果迭代次数就是字符串的长度。

我的面试官告诉我,最坏情况下的时间复杂度是指数级的。

谁能帮我澄清一下?如果我错了,我需要了解原因。

【问题讨论】:

  • "但是递归调用只在字符串中还没有被迭代过的部分,所以迭代的最终结果数就是字符串的长度" - 你如何证明这一步?
  • 看起来像“三角形 n”。一个长度为 5 的字符串看起来会循环 15 次。 6 会循环 20 次。似乎不是指数的。
  • 你确定面试中的代码有一个无条件的return SetContainsString(remainingSegment, setOfStrings);,而不是只有在递归调用返回true时才返回那个结果?
  • @user2357112 我开始知道我哪里出了问题。谢谢!在根情况下,它仍然会遍历字符串的所有元素,即使递归调用仅遍历一个子集——当进行递归调用时,没有什么可以打破根循环。所以我们在根案例中有 N,第一个递归中有 N-1,下一个递归有 N-2,等等。
  • 该函数似乎很可能是为了检查searchString 是否是setOfStrings 中的字符串连接(允许重复),在这种情况下,无条件的return SetContainsString(remainingSegment, setOfStrings); 应该是@ 987654327@,并且该函数将具有指数运行时间。

标签: string algorithm time-complexity big-o


【解决方案1】:

我认为你的面试官在这里是不正确的。以下是我认为时间复杂度不是指数的原因:

  • 对该函数的每次调用都会进行零次或一次递归调用。
  • 每次递归调用都会将字符串的长度至少减少一。

这限制了 O(n) 处的递归调用总数,其中 n 是输入字符串的长度。每个单独的递归调用都做多项式的工作,所以完成的总工作是一些多项式。

我认为你的面试官在这里感到困惑的原因是上面给出的代码 - 我认为它应该检查一个字符串是否可以分解成一个或多个单词 - 在所有情况下都不能正常工作。特别是,请注意递归的工作方式总是乐观地抓取它找到的第一个前缀是一个单词,并假设它抓取的是正确的分割单词的方法。但是想象一下你有一个像“苹果酱”这样的词。如果你去掉“a”并尝试递归地形成“pplesauce”,你会错误地报告这个词不是复合词,因为没有办法分解“pplesauce”。要解决此问题,您需要将递归调用更改为以下内容:

if (SetContainsString(...)) return true;

这样,如果您选择了错误的拆分,您将继续检查您可以进行的其他可能拆分。在最坏的情况下,代码的变体确实会花费指数级的时间,因为它可能会多次重新访问相同的子字符串,我认为这就是面试官错误地认为你在做的事情。

【讨论】:

  • "对函数的每次调用都会进行零次或一次递归调用。"根据循环中的条件,可以多次调用该函数。我错过了什么?
  • @CodeYogi 请注意,我们在进行递归调用后立即从函数返回,因为我们返回递归调用产生的值。这意味着一旦进行其中一个递归调用,循环就会停止运行。在我稍后提到的代码的更正版本中,我们并不总是在调用后立即返回,这就是指数运行时的来源。
  • 如果您能解释一下如何计算指数时间复杂度,那就太好了。
猜你喜欢
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 2020-03-21
  • 2021-11-11
  • 2016-03-20
  • 2020-12-16
相关资源
最近更新 更多