两种算法的大O分析

时间:2017-01-27 21:30:26

标签: c++ algorithm recursion big-o

我为leetcode problem 17创建了两个解决方案,要求您从电话号码组合中生成所有可能的文本字符串,例如"3"会产生["d","e","f"]

我的第一个解决方案使用递归算法生成字符串,如下所示:

class Solution {
public:
    void fill_LUT(vector<string>& lut) {
        lut.clear();
        lut.push_back(" ");
        lut.push_back("");
        lut.push_back("abc");
        lut.push_back("def");
        lut.push_back("ghi");
        lut.push_back("jkl");
        lut.push_back("mno");
        lut.push_back("pqrs");
        lut.push_back("tuv");
        lut.push_back("wxyz");
    }

    void generate_strings(int index, string& digits, vector<string>& lut, vector<string>& r, string& work) {
        if(index >= digits.size()) {
            r.push_back(work);
            return;
        }

        char idx = digits[index] - '0';
        for(char c : lut[idx]) {
            work.push_back(c);
            generate_strings(index+1, digits, lut, r, work);
            work.pop_back();
        }
    }

    vector<string> letterCombinations(string digits) {
        vector<string> r;
        vector<string> lut;
        fill_LUT(lut);

        if(digits.size() <= 0)
            return r;

        string work;
        generate_strings(0, digits, lut, r, work);

        return r;
    }
};

我对big-O有点生疏,但在我看来,递归调用的空间复杂度为O(n),即缓冲区字符串的最大深度O(n),以及结果字符串O(n*c^n)。这会加起来为O(n+n*c^n)吗?

对于时间复杂度,我有点困惑。递归的每个级别执行c推送+弹出+递归调用乘以下一级别的操作数,因此听起来像c^1 + c^2 + ... + c^n。此外,c^n长度字符串有n个重复项。 如何将其合并为一个漂亮的大O代表?

第二个解决方案将结果数视为混合基数,并将其转换为字符串,因为您可以执行int到十六进制字符串转换:

class Solution {
public:
    void fill_LUT(vector<string>& lut) {
        lut.clear();
        lut.push_back(" ");
        lut.push_back("");
        lut.push_back("abc");
        lut.push_back("def");
        lut.push_back("ghi");
        lut.push_back("jkl");
        lut.push_back("mno");
        lut.push_back("pqrs");
        lut.push_back("tuv");
        lut.push_back("wxyz");
    }

    vector<string> letterCombinations(string digits) {
        vector<string> r;
        vector<string> lut;
        fill_LUT(lut);

        if(digits.size() <= 0)
            return r;

        unsigned total = 1;
        for(int i = 0; i < digits.size(); i++) {
            digits[i] = digits[i]-'0';
            auto m = lut[digits[i]].size();
            if(m > 0) total *= m;
        }

        for(int i = 0; i < total; i++) {
            int current = i;
            r.push_back(string());
            string& s = r.back();
            for(char c : digits) {
                int radix = lut[c].size();
                if(radix != 0) {
                    s.push_back(lut[c][current % radix]);
                    current = current / radix;
                }
            }
        }

        return r;
    }
};

在这种情况下,我认为空间复杂度O(n*c^n)与第一个解决方案类似,减去缓冲区和递归,第一个for循环的时间复杂度必须为O(n),另外{ {1}}为每个可能的结果创建结果字符串。最终的大O是O(n*c^n)我的思维过程是否正确?

修改:要为代码添加一些说明,请设想一个O(n+n*c^n)的输入字符串。第一个递归解决方案将使用参数"234"调用generate_strings(0, "234", lut, r, work)是一个查找表,可将数字转换为相应的字符。 lut是包含结果字符串的向量。 r是执行工作的缓冲区。

第一个递归调用将看到索引work数字为0,与2对应,将"abc"推送到a,然后调用带有work参数的generate_strings。一旦通话恢复,它将把(1, "234", lut, r, work)推到b并冲洗并重复。

work等于index的大小时,会生成一个唯一的字符串,并将该字符串推送到digits

对于第二个解决方案,输入字符串首先从它的ascii表示转换为它的整数表示。例如,r已转换为"234"。然后代码使用这些索引来查找查找表中相应字符的数量,并计算结果中的字符串总数。例如如果输入字符串为"\x02\x03\x04",则"234"对应2,其中包含3个字符。 abc对应3,其中包含3个字符。 def对应4,其中包含3个字符。可能的字符串总数为ghi

然后代码使用计数器来表示每个可能的字符串。如果3*3*3 = 27i,则首先会找到15 15 % 3进行评估,该0对应第一个数字的第一个字符(a)。然后将15除以3 55 % 32,与第二个数字的第三个字符f对应。最后将5除以3,然后获得11 % 31,与第三个数字h的第二个字符对应。因此,与数字15对应的字符串为afh。这是针对每个数字执行的,结果字符串存储在r

1 个答案:

答案 0 :(得分:2)

递归算法:

空间:每个递归级别为O(1),并且有O(n)级别。因此,递归是O(n)。结果的空间是O(c ^ n),其中c = max(lut [i] .length)。算法的总空间为O(c ^ n)。

时间:设T(n)为长度为n的数字的成本。然后我们有递归公式:T(n)&lt; = c T(n-1)+ O(1)。求解该等式得到T(n)= O(c ^ n)。

哈希算法:

空格:如果你需要空间来存储所有结果,那么它仍然是O(c ^ n)。

时间:O(n + c ^ n)= O(c ^ n)。

我喜欢散列算法,因为如果问题要求您提供特定的字符串结果(假设我们按字母顺序排序)会更好。在这种情况下,空间和时间仅为O(n)。

这个问题让我想起了一个类似的问题:生成集合{1,2,3,...,n}的所有排列。散列方法更好,因为通过逐个生成排列并处理它,我们可以节省大量空间。