【问题标题】:Is the time complexity of the code snippet O(k*n^2)?代码片段的时间复杂度是O(k*n^2)吗?
【发布时间】:2017-08-28 14:48:58
【问题描述】:

我正在解决下面的 LeetCode 问题:

给定一个数字字符串,返回该数字可以表示的所有可能的字母组合。下面给出了数字到字母的映射(就像电话按钮上一样)。

我试图了解高度赞成的解决方案的复杂性:

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        std::vector< string > vec;
        if(digits.length()==0)
            return vec;

        std::queue< std::string > ans;
        std::string mapping[]={"0", "1", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

        ans.push("");                           
        for(int i=0; i<digits.length(); i++)
        {
            int x = (digits[i]-'0');
            while(ans.front().length()==i)
            {
                std::string t=ans.front();
                ans.pop();                      
                for(int j=0; j<mapping[x].length(); j++)
                {
                    ans.push(t+mapping[x][j]);
                    //cout<<t+mapping[x][j]<<"\n";
                }
            }
        }

        std::string val;
        int size=ans.size();
        for(int i=0; i<size; i++)
        {
            val=ans.front();
            ans.pop();
            vec.push_back(val);
        }
        return vec;
    }
};

我的理解如下:

如果n 是位数,那么我认为复杂度是O(k*n^2),其中k 是映射长度(2 -> 'abc',3 -> 'def' 等等) .

最外层的for循环执行n次。在每次迭代中,我们访问队列中长度等于i(最坏情况下为n)的前面元素。这使它成为O(n^2)。最里面的 for 循环运行固定次数(等于映射长度,比如k)。此外,push() 操作需要固定时间。因此,总复杂度为O(k*n^2)

我的理解正确吗?

【问题讨论】:

  • 评论还不够——字符串5^nn 连续出现5 的实例,例如555555、. ..) 表示3^n 字符串,因为每个数字都可以对应三个字母中的任何一个。实际上,最坏的情况是长度为n 的字符串9s 和7s,给出4^n 的可能性。如果您返回 4^n 字符串(实际字符串,而不仅仅是此类字符串的数量),我认为比指数时间做得更好是不现实的。如果您要返回某种压缩表示形式(例如数字字符串本身!)或元数据(字符串数量),那就不同了。
  • @Patrick87,您能详细说明一下吗?
  • O(k^n) 是我想出的。
  • @iBug,怎么会这样?你能详细说明一下吗?

标签: c++ algorithm time-complexity


【解决方案1】:

让我们使用以下符号:

  • n 是输入的长度;
  • k 是映射的最大长度,在您的情况下是 4"pqrs""wxyz");

...映射的长度(mapping 中的元素数量)与这里的复杂性无关。

std::queue 上的pushpop 的复杂度为O(1)(因为您使用的是deque)。我会假设字符串的连接是O(1)(实际上不是这样)。

简单地说,我们可以看到:

  • 内部for 循环将始终执行k 迭代;
  • 内部while 循环将迭代ans.size()

因此,在外部for 循环的每次迭代中,内部for 循环的主体被评估k * ans.size() 次,并且ans.size() 在每次迭代中都以k 的倍数增长,所以你有一个指数行为:

  1. i = 0, ans.size() = 1 -- k 迭代次数;
  2. i = 1, ans.size() = k -- k * k 迭代;
  3. i = 2, ans.size() = k * k -- k * k * k 迭代次数;

在最后一次迭代 (i = n - 1) 期间,内部 for 循环的主体将被评估 k ^ (n - 1) 次,因此总共将评估此主体:

k + k^2 + k^3 + ... + k^(n - 1) = (k^n - 1) / (k - 1) - 1

所以内部for 循环被评估O(k^n) 次,所以这个函数的复杂度是至少 O(k^n) - 你需要考虑连接和复制字符串不是O(1)(在这种特定情况下,您可以计算每次迭代的复杂度,基本上是第 i 次迭代的O(i + 1))。

这是一个指数复杂度,比您的多项式O(k.n^2) 复杂度要差得多。

【讨论】:

  • 好的,所以我错过了一件重要的事情——我假设队列ans 的最大值是n,这是不正确的,因为它在每次迭代时都会扩展。您能否详细说明您是如何从“中得出其 exponentiation 的,因此在外部 for 循环的每次迭代中,内部 for 循环的主体都被评估 k * ans.size() 次,并且ans.size() 每次迭代都会增长 k 倍"?
  • @UmedhSinghBundela 如果您在迭代iM(i + 1) = k * M(i) 期间执行M(i) 操作(每次迭代的操作数随着因子k 而增长),那么您将具有指数行为(仅来自geometric series 的总和。
  • 所以在这种情况下,每次迭代的操作数会以 k 的倍数增长,因为我们会在每次迭代中增加双端队列的大小,对吗?跨度>
  • @UmedhSinghBundela 是的 - 这在很多算法中都很典型,如果你有一个容器 C 并且在每次迭代中你 1) 遍历整个容器,并且 2) 乘以容器的大小通过某些东西(这里是k),那么你就有了指数行为。另一方面,如果将容器的大小增加一个常数(或有界值),则具有多项式行为。
  • 顺便说一句,结果的大小是k ** n 所以复杂度应该至少是这个数字。
猜你喜欢
  • 2015-02-18
  • 2015-08-22
  • 1970-01-01
  • 2018-12-12
  • 1970-01-01
  • 2015-06-14
  • 1970-01-01
  • 1970-01-01
  • 2020-07-15
相关资源
最近更新 更多