应对"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
我明天有一个编程面试,我正在练习。我读到一个常见的问题是使用原型找到字符串中第一个重复的字符
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可以用来表示"未找到")。
- 运行同一解决方案的另一个项目的项目
- Project Euler问题4的错误解决方案
- 计算每个节点的树高,帮助我解释这个代码解决方案
- C++:Application.cpp中抛出了未解析的外部符号(解决方案在问题的末尾,供未来的读者参考)
- visual c++,如何获取解决方案目录中的代码
- 有没有办法在远程设备上打开和编辑visual Studio 2017解决方案
- C++Matching Brackets 2解决方案不起作用
- 在 ubuntu3 上C++ goto 定义有什么解决方案吗16.04?
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 我的固定时间步长与增量时间和插值的解决方案是错误的吗?
- 无法在问题解决方案中执行输出逻辑
- 最大的回文产品 - 程序未运行,编写解决方案但无法理解问题
- 从预序遍历构造 bst 的 c++ 和 python 解决方案之间的区别
- 在一个解决方案中针对第三方静态库 (Creo) 的不同版本(版本)进行构建
- 如何巧妙地编写两个函数——一个用于检查是否存在解决方案,另一个用于获取所有解决方案
- 使用 Git 处理 C++ Visual Studio 2019 解决方案的外部依赖项源代码管理的最佳方法是什么?
- N-queen问题:无法弄清楚为什么我的解决方案不起作用
- 从排序数组中删除重复项,具有不同代码方式的相同解决方案具有不同的输出
- 使用XOR查找O(n)-解决方案中的两个字符串是否为变位符
- 应对"Find the first repeated character in a string"挑战的最紧凑、高效、可读和智能的解决方案