应对"Find the first repeated character in a string"挑战的最紧凑、高效、可读和智能的解决方案

Most compact, efficient, readable and clever solution for the "Find the first repeated character in a string" challenge

本文关键字:高效 解决方案 智能 挑战 first the Find repeated character string in      更新时间:2023-10-16

我明天有一个编程面试,我正在练习。我读到一个常见的问题是使用原型找到字符串中第一个重复的字符

size_t FindFirstRepeatedChar ( char * );

我能想到的最好的解决方案是

#include <string.h>
#include <vector>
char FindFirstRepeatedChar ( char * S )
{
/* Returns the index of the first repeated character in the string S. 
Assume S does indeed contain a repeat.
*/
std::vector<bool> booVec(128,false)
for (char * c1(S), * c2(S+strlen(S)); c1 != c2; ++c1)
{
size_t i((size_t)*c1); 
if (booVec[i] == false) booVec[i] = true;
else return (char)i;
}   
}

但我想知道

(1) 这错了吗?

(2) 如果没有错的话,有没有什么方法可以节省电子,即进一步优化它?

(3) 有没有一个标准的库算法,我可以利用它在一行中解决它?

通常面试不会期望你说这个库已经做到了,因为他们想检查你是否可以编写代码和算法。

至于改进它,我想在一次采访中说,他们可能不是要求最好的优化,而是要求更容易阅读的东西(这也意味着你在写它时出错的风险更小)。

我可能会在第一个循环中写一个非常简单的嵌套循环。它比较慢,但写起来很容易。如果他们要求我做得更快,我稍后会转向你的解决方案。

该程序中的主要错误是,您输入了一个char(通常为8位),而您的矢量只存储128个元素,因此如果字符串中有非ASCII字符,它将崩溃。

您的代码有一些缺陷。

首先,char可能是有符号的或无符号的,并且由于符号扩展,将signed char直接强制转换为size_t可能会产生意想不到的后果。当然,这只是有趣的,如果您接受任何大于127的字符可能存在,而您的代码目前根本不支持这些字符——如果不是这样,您可以进一步减小vector的大小。

第二,执行两次迭代,其中一次迭代命中整个字符串:strlen只能通过在整个字符串上运行来执行其工作。。。我们可以在这里添加更多的技巧,将booVec[0]的初始值设置为true:这将在第一次出现时触发return,从而使我们能够在提高安全性的同时完全消除循环退出条件。

第三,您的代码不支持unicode,尽管这可能是经过设计的。

通过用char const*s替换char*s,我们也允许代码处理例如字符串文字。

最后,您可以将vector的用法替换为数组。这将略微增加运行时内存的使用量,但可能会显著提高性能(因为不需要堆分配,也不需要位操作来从位向量中提取位)。

char FindFirstRepeatedChar(char const* S)
{
bool booVec[(std::numeric_limits<char>::max)()] = { true };
for (char const* c(S); ; ++c)
{
size_t i(static_cast<unsigned char>(*c)); 
if (booVec[i] == false) booVec[i] = true;
else return *c;
}
}

最后,对于标准库:由于标准库算法需要一个结束迭代器,如果没有额外的技巧(例如,编写一个具有有趣语义的迭代器类型),它们的性能就会降低,这需要比整个示例更多的代码。

我正在考虑使用正则表达式匹配的方法。这是有效的,假设有一个重复。perl -pe '$_=reverse;s/(.*(.).*2.*)/2/s'。不过,如果我能去掉反面,那就更好了。

我可以试试这个:

char FindFirstRepeatedChar(char *S) {
size_t i = 1;
while (!memchr(S, S[i], i)) i++;
return S[i];
}

重复必须出现在前256个字符中(根据鸽子洞原则),因此此代码总是在O(1)时间内运行。

问题:

  • 其他人已经观察到:负字符问题是最糟糕的,因为它会崩溃。使用vector是低效的,因为它动态地分配内存,这通常是一种昂贵的操作。通过动态测试空字符,可以避免只为找到字符串的末尾而对字符串进行一次迭代。

  • 我主要担心的是,您默默地更改了函数的返回类型,而不是必需/指定的类型。在这种情况下,这非常糟糕,因为只阅读规范/文档的用户会认为FindFirstRepeatedChar返回size_t。他能编码

    size_t firstRepIndex = S[FindFirstRepeatedChar ( char * S )];
    

    这甚至不会导致警告,因为char具有到size_t的整数提升路径。如果S较短,则该"索引"(在1..255范围内)可能超出范围,因此用它索引S将为UB。

    返回一个char也比索引传递更少的信息(重复在哪里?)。最后,如果某一天您可能想要测试未知字符串,则索引具有"无重复"值(0或最后一个元素索引之后的值)。字符没有"转义值",因为在一般情况下,字符可以包含任何位模式,包括0(尽管在这种情况下,由于不存在长度参数,我们必须假设0终止的字符串参数,以便0可以用来表示"未找到")。

相关文章: