从字符串中删除指定的字符 - 高效的方法(时间和空间复杂性)

时间:2012-01-12 22:06:23

标签: c++ string algorithm

问题是:从给定字符串中删除指定的字符。

Input: The string is "Hello World!" and characters to be deleted are "lor"
Output: "He Wd!"

解决这个问题涉及两个子部分:

  1. 确定是否要删除给定字符
  2. 如果是,则删除字符
  3. 要解决第一部分,我正在读取要删除的字符到std::unordered_map,即我解析字符串“lor”并将每个字符插入到hashmap中。稍后,当我解析主字符串时,我将查看此hashmap,并将每个字符作为键,如果返回的值不为零,则从字符串中删除该字符。

    问题1:这是最好的方法吗?

    问题2:哪个更适合这个问题? std::mapstd::unordered_map?由于我对订购不感兴趣,我使用unordered_map。但创建哈希表的开销是否更高?在这种情况下该怎么办?使用map(平衡树)或unordered_map(哈希表)?

    现在进入下一部分,即删除字符串中的字符。一种方法是删除字符并将数据从该点移开一个位置。在最坏的情况下,我们必须删除所有字符,这将需要O(n ^ 2)。

    第二种方法是仅将所需字符复制到另一个缓冲区。这将涉及分配足够的内存来保存原始字符串并逐个字符地复制而忽略要删除的字符串。虽然这需要额外的内存,但这将是O(n)操作。

    第三种方法是从第0个位置开始读取和写入,每次读取时都会递增源指针,只在我写入时递增目标指针。由于源指针总是在目标指针之前或之前,我可以在同一个缓冲区上写入。这节省了内存,也是O(n)操作。我正在做同样的事情,最后调用resize删除其他不必要的字符?

    这是我写的函数:

    // str contains the string (Hello World!)
    // chars contains the characters to be deleted (lor)
    void remove_chars(string& str, const string& chars)
    {
        unordered_map<char, int> chars_map;
    
        for(string::size_type i = 0; i < chars.size(); ++i)
            chars_map[chars[i]] = 1;
    
        string::size_type i = 0; // source
        string::size_type j = 0; // destination
        while(i < str.size())
        {
            if(chars_map[str[i]] != 0)
                ++i;
            else
            {
                str[j] = str[i];
                ++i;
                ++j;
            }
        }
    
        str.resize(j);
    }
    

    问题3:我可以通过哪些不同的方式来改进此功能。或者我们能做到最好吗?

    谢谢!

4 个答案:

答案 0 :(得分:3)

干得好,现在了解标准库算法并提升:

str.erase(std::remove_if(str.begin(), str.end(), boost::is_any_of("lor")), str.end());

答案 1 :(得分:2)

假设您正在学习算法,而对图书馆解决方案不感兴趣:

当可能的键数很大时,哈希表是最有价值的,但您只需存储其中的一些。如果从数字序列中删除特定的32位整数,则哈希表是有意义的。但是对于ASCII字符,这太过分了。

只需制作一个256个bool的数组,并为要删除的字符设置一个标志。它仅对每个输入字符使用一个表查找指令。散列图至少涉及一些计算散列函数的指令。在空间方面,一旦你将所有辅助数据相加,它们可能不再紧凑。

void remove_chars(string& str, const string& chars)
{
    // set up the look-up table
    std::vector<bool> discard(256, false);
    for (int i = 0; i < chars.size(); ++i)
    {
        discard[chars[i]] = true;
    }

    for (int j = 0; j < str.size(); ++j)
    {
        if (discard[str[j]])
        {
            // do something, depending on your storage choice
        }
    }
}

关于您的存储选择:根据您是否需要保留输入数据,在选项2和3之间进行选择。 3显然是最有效的,但你并不总是想要一个就地程序。

答案 2 :(得分:1)

这是一个具有许多优势的KISS解决方案:

void remove_chars (char *dest, const char *src, const char *excludes)
{
    do {
        if (!strchr (excludes, *src))
            *dest++ = *src;
    } while (*src++);
    *dest = '\000';
}

答案 3 :(得分:0)

您可以在strcspnstrspn之间乒乓,以避免需要哈希表:

void remove_chars(
    const char *input, 
    char *output, 
    const char *characters)
{
    const char *next_input= input;
    char *next_output= output;

    while (*next_input!='\0')
    {
        int copy_length= strspn(next_input, characters);
        memcpy(next_output, next_input, copy_length);

        next_output+= copy_length;

        next_input+= copy_length;
        next_input+= strcspn(next_input, characters);
    }
}