如何提高加扰代码的性能?

How can I improve performance of scramble code?

提问人:Roofin88 提问时间:10/31/2023 最后编辑:Roofin88 更新时间:11/1/2023 访问量:159

问:

我需要完成函数 scramble(str1, str2),如果可以重新排列 str1 字符以匹配 str2,则返回 true,否则返回 false。通过哪种方法可以提高此代码的性能? 例如,如果我在 str1 和 str2 中有 200k+ 个字符,则性能非常糟糕。

bool scramble(const string& s1, const string& s2) 
{
    string reference = s2;
    string inputStr = s1;
    size_t refLen = reference.length();
    size_t lenCounter = 0;

    for (auto n = 0; n < reference.length(); n++)
    {
        auto tempLetter = reference[n];
        size_t letterPos = inputStr.find(tempLetter);
        if (letterPos != string::npos)
        {
            lenCounter++;
            inputStr.erase(letterPos, 1);
            if (lenCounter == refLen)
            {
                return true;
            }
        }
        else
            return false;
    }
    if (refLen == lenCounter)
        return true;
    else
        return false;
}

这是一个现成的解决方案,可以满足我的要求。@Dave提供的此解决方案的想法

bool scramble(const string& s1, const string& s2)
{
    string ref = s2;
    string input = s1;
    string result = "";
    char tempLetter = '\0';
 
    sort(ref.begin(), ref.end());
    sort(input.begin(), input.end());
   
    map<char, int> letters;

    for (auto n = 0; n < input.length(); n++)
    {
        tempLetter = input[n];
        map<char, int> ::iterator it = letters.find(tempLetter);
        if (it == letters.end())
        {
            letters.insert(make_pair(tempLetter, 0));
            letters.at(tempLetter) = 1;
        }
        else
        {
            letters.at(tempLetter) += 1;
        }
    }
    for (auto i = 0; i < ref.length(); i++)
    {
        tempLetter = ref[i];
        map<char, int> ::iterator it = letters.find(tempLetter);
        if (it != letters.end() && letters.at(tempLetter) >=0)
        {
            letters.at(tempLetter) -= 1;
            if (letters.at(tempLetter) < 0)
                return false;
            result.push_back(tempLetter);
        }
    }
    if (result!=ref)
        return false;
    else
        return true;
}
C++ 字符串 算法 性能 优化

评论

2赞 user12002570 10/31/2023
codereview.stackexchange.com
2赞 n. m. could be an AI 10/31/2023
您知道如何快速检查字谜吗?如果没有,请查一下,这是一个非常著名的简单问题,每个人和他们的狗都有一个版本。
4赞 Peilonrayz 10/31/2023
@n.m.couldbeanAI 通常你是对的。但是,对于无法扩展的程序,我们有一个例外。如果代码在小输入上正常工作(输出正确的值),则代码将属于我们的缩放规则。此外,如果您仍然不相信我,我想向您指出我们的 time-limit-exceeded 标签。感谢您帮助我们避免偏离主题的问题 Code Review。
1赞 MPIchael 10/31/2023
欢迎来到 Scicomp!您可以测试的一种方法是对两个字符串进行排序,然后开始查找。这样一来,您可以大大减少出现负面结果的时间,但代价是对两个字符串进行排序。尝试类似的东西:std::sort(std::begin(array), std::end(array))
3赞 MPIchael 10/31/2023
另一个想法是做一次传递,你数“a”、“b”的数量......并保留一张桌子。然后对第二个数组执行相同的操作。然后检查数组中是否有足够的“a”和“b”。这应该会降低线性的复杂性,但会消耗一些记帐内存。

答:

1赞 Avi Lachmish 10/31/2023 #1

出于两个原因,我会写得有点不同:

  1. 可读性和可维护性
  2. 复杂性

比方说,那应该更大.m = sizeof s1;n = sizeof s2;nm

首先对字符串进行排序将花费 O(nlog(n)),但随后,在一次传递中,您可以获得输出,这意味着我的加扰函数复杂度为:

O(n+nlogn) = O(nlongn)

但是,在您的情况下,复杂性是 .O(n*m)

我是这样写的:

bool is_scramble(std::string s1, std::string s2) 
{
    if (s2.size() < s1.size()) return false;

    std::ranges::sort(s1);
    std::ranges::sort(s2);

    return std::ranges::includes(s2, s1);
}

正如评论中提到的,您可以进行计数排序(在 ASCII 或 EBCDIC 的限制下),这在时间复杂度方面甚至更便宜,然后编写以下代码。

它确实需要 256 字节的额外存储空间(有 ASCII 或 EBCDIC 的限制)。恕我直言,它的可读性也较差。

bool is_scramble2(std::string s1, std::string s2) 
{
    // s1 needs to be contain in s2
    if (s2.size() < s1.size()) return false;

    constexpr int maxChar = 256; // Assuming ASCII character set
    std::array<int, maxChar> count = {0};

    // Count the occurrences of each character in the string
    for (char c : s2) {
        count[static_cast<int>(c)]++;
    }

    for(const auto c : s1) {
        if (count[static_cast<int>(c)] <= 0) return false;
        count[static_cast<int>(c)]--;
    }
    return true;
}

评论

0赞 Jarod42 10/31/2023
使用计数排序甚至可以(甚至不需要排序部分,因为只有计数部分就足以检查包含)。O(256 * n)
0赞 Avi Lachmish 10/31/2023
是的,你是对的,但你假设 ASCII 字符并且你使用了 256 个额外的字节
0赞 Jarod42 10/31/2023
我不认为 ascii(EBCDIC 也会得到支持)。
0赞 Avi Lachmish 10/31/2023
对不起,你又是对的......将更新我的答案
1赞 Dave 10/31/2023 #2

如果字符串的长度不同,则返回 false。

使用计数哈希。

对于第一个单词,递增地图中每个字母的计数。对于第二个,递减每个字母的计数。

递减时,如果字母递减到零以下,则返回 false。如果坚持到最后,则返回 true。

O(n) 表示 n 个字母。

您可以使用一个整数数组,在相关字母表中每个字符有 1 个索引,而不是计数哈希,并将其用于计数。

评论

2赞 Roofin88 11/1/2023
非常感谢,你的回答真的帮助了我。我已经针对我的情况进行了优化,效果很好。也感谢您没有提供现成的解决方案,这对我的 C++ 学习非常好)
1赞 Ben Voigt 11/1/2023
“最后,如果总数没有回落到零,则返回 false”,或者提前退出 if ,在这种情况下,您根本不需要进行任何散列。a.size() != b.size()
0赞 Dave 11/1/2023
@BenVoigt 好点子。编辑。谢谢。
0赞 Vlad Feinstein 11/3/2023
你真的需要地图吗?我会为每个字母使用一组计数器。
1赞 Dave 11/3/2023
@VladFeinstein 这就是我最后一句话的意思:使用一个整数数组,而不是计数哈希,每个字符有 1 个索引。这还不清楚吗?