How to reverse a string in O(1) complexity (runtime)?
今天我接受了一次面试,要求我在一次手术中扭转局面。我觉得不到O(N)是不可能的。顺便说一下,我得到了一个线索,"交换"!所以。…有答案吗?
- 示例输入:"abcdefghi"(或任何字符串)
- 样本输出:"IHGFEDCBA"
- 不能使用内置函数。(例如:strrev())
- 答案并不像打印/迭代那样复杂。
- 有细绳吗?还是某个特定的?它是指一些代码,还是只是理论上的?
- 请先选一种语言!
- 我想采访者的意思是O(1)空间,而不是运行时
- 嗯,我可以在零时间内反转一个字符串"aaaaa"…
- 有一个输入"abcdefghi"和一个输出"ihgfedba",如果有或特定的呢?@尤金尼什。
- 也许绳子的尺寸是2。它可能是回文串。
- ^同上。但考虑到你的字符串,看来@lashane猜测是正确的。
- 抱歉,不,不是回文@roihatam
- 如果将字符串定义为连续的字节序列,则反转字符串只是更改序列发生顺序的定义。
- 对于O(1)空间复杂性,交换第一个和最后一个字符,然后向内移动到字符串的中间。这就需要一个字符的额外存储来执行交换。
- 但是你仍然需要找到字符串的结尾,所以除非你得到长度,否则它仍然是O(n)
- 没有其他细节,并且假设"单个操作"意味着CPU指令不是所选语言stdlib中某个东西的"单个函数调用"…我看不出在O(1)时间内反转任意字符串的任何方法。o(1)空间,当然,通过交换字符,如@cdhowie所提到的。
- @Williambursell大多数语言(C是一个显著的例外)都提供了一个规范的字符串类型,它确实知道自己的长度。
- @Adrian我可以考虑交换物理内存芯片中的地址行:)
- 面试官可能想看看你的想法。
- 另一种猜测——可能采访者的意思是"你将如何实现字符串类,反向是O(1)运行时?"-然后,答案是"用字符串、长度、位标志存储字符数组以指示方向,因此在这种实现中,反向是O(1)空间/时间"。
- 哦,等等。这个怎么样?
- @尤金尼什。很好的发现,但是你需要用这个符号"预先"你的字符串,这意味着O(N),因为复制:(但是如果你存储的字符串总是在开始时用LTR/RTR标记-那么它实际上是O(1):)
- 您可以在常量时间内反转一个XOR链接列表,因此如果您表示一个字符串,则可以反转一个XOR链接的字符列表。
- @哈罗德…以及许多其他设计为在o(1)中反转的更简单的数据结构:
- @尤金尼什。好吧,继续说,我不认识其他人
- @哈罗德,一个包含"原始"字符串和"反向"字符串的数据结构怎么样?
- @尤金尼什。好的,当然,还有吗?
- @哈罗德,我想你是在挖苦我,所以我停在这里。
- @尤金尼什。不,说真的,这里可能有很多有趣的技巧,那为什么不贴出来呢?好吧,希望这比一直跟踪反向版本更有趣,我不认为这是一种数据结构,而更像是一种特殊的技巧,但这是一种有用的技术
- @Harold尽管将长度存储在字符串本身的某个位置,但您可以只存储指向第一个/最后一个符号的指针、使用普通的双链接列表、使用堆栈、甚至treemap
- @哈罗德,为什么不是数据结构?这些技巧被广泛使用。例如,在min堆和二进制树中都有相同的数据。为了从O(log(n))搜索和O(1)查找最小值(好吧,有指针的话,它可以是相同的数据)中获益。
- @尤金尼什。是的,我通常也不会将它们视为新的数据结构,而是将它们视为串联使用的两个数据结构。但这只是术语。
- 听起来更像是一个代码难题的问题。
在O(N)时间中不能反转字符串,但是,可以使用O(1)空间复杂性来反转字符串。
reverse a string in single operation
号
很可能是reverse it in one-liner,因为还不清楚"操作"到底是什么。
可以使用STD::反向算法在一行中反转字符串:
1 2 3 4 5 6 7 8 9
| #include <string>
#include
int main()
{
std::string str ="Hello world!";
std::reverse(str.begin(), str.end());
std::cout <<"reverse is: "" << str << '"' << std::endl;
} |
输出:
1
| reverse is:"!dlrow olleH" |
号
或者你也可以用一个简单的循环来完成它:
1 2
| for (size_t i=0; i<str.size()/2; ++i)
std::swap(str[i], str[str.size()-1-i); |
然而,这是O(N)运行时和O(1)空间(就像std::reverse一样)。
通常,使用简单的反向字符串算法问题进行采访并不是关于一些技巧,很可能您的采访者希望看到实现这种循环。另外,不要忘记面试官也是人,有时他们只是犯错误,要求不可能。或者,他们只是想让你说不可能在O(1)中反转一个序列。
- 谁说"一行"?
- 这不是问题的答案。
- 这是一个单一的操作(问题的一部分),而不是O(1)(问题的另一部分)。
- @尤金尼什。我想"单一操作"就是这个意思。不能在一个操作中反转字符串。
- 计算复杂性的概念与给定语言中的函数无关。否则,我们可以想象一种语言,其中的一切都是O(1)。
- @是的,但你为什么这么说?
- 最初的问题是,"能把O(1)中的字符串颠倒吗?"但是,你给出的答案显然是关于"如何在C++中反转一个字符串"的问题。您提到上面特定算法的复杂性的事实仍然不能回答最初的问题。我评论说,你的算法没有证明任何关于复杂性的东西,因为我们不知道底层函数是如何工作的,也不知道它是否使用了理论上最优的算法。
- @digiproc好吧,这并不是一个谜,这个算法的复杂性一般来说要反转一个序列,与实现无关。如果你愿意的话,你可以继续质疑一切。顺便说一句,如果你喜欢的话,试着想出一个不是O(N)的实现。
你做不到。即使在某个地方有一些现有的函数可以实现它,但在内部它仍然需要O(N)。虽然从技术上讲,交换的复杂性是偶数个字符的O(n)和奇数个字符的O(n-1)(因此可以"从技术上"说交换3个字符的字符串是O(1)),但每个操作都有点复杂,因为在交换另一个字符时,必须将一个交换字符存储在局部变量中。因此,出于实际原因,交换将更慢,而不是更快。
- O(n - 1)与O(N)完全相同。它是O(1),对于任何长度不变的字符串,而不仅仅是3。
- 没有"以东十一号"(8),就是"以东十一号"(4)。如果你想计算在那里有多少个操作,可以是N/2或N/2-1。
- O(n-1)比O(n)更精确(对于奇数字符)。但是,如果您更喜欢不太精确的值,那么就直接开始吧。
- 不,如果你坚持认为O(n-1)更精确,面试就会失败。而且,正如我指出的,它不是n-1,而是n/2-1
- 为什么我要在一个更喜欢不精确而不是精确的地方工作?
- 其次,计算复杂性是步骤数量的指示,理论上,每个步骤可以用一个硬件时钟周期(禁止多线程)实现。因此,它是n-1,而不是n/2-1。
- 你不正确地使用了"大哦"符号。O(f(n))表示一类函数。表示同一类的两个函数中的一个比另一个更精确,这显然表明您不理解这个符号的实际含义。
把绳子倒过来?只需将它从rbegin()迭代到rend()。或者std::copy的范围是一个新的字符串。一个"颠倒的字符串"和原来的字符串一样,只是读另一种方式。
- 这将是O(N)次,因为它是基于字符串的长度。
- "迭代器"不像O(1)那样响铃
- 尽管听起来有点轻率,但我还是想到了同样的事情。
- 考虑到这个问题,我认为这个答案没有那么糟糕。无论如何,您需要对字符串进行迭代以将其打印出来,这样计算中就不应该包含这一点。因此,打印字符串通常是将begin()打印到end()。打印的反面是将rbegin()打印到rend()。所以字符串在o(1)中被颠倒。
- @"小于0(1)"是什么意思?输入越大,速度越快?迭代一个字符串将是O(N)。
- 我认为Jesper试图说的是,"反转一个字符串"如果可以定义为反向解释一个现有的字符串,那么它实际上就是一个"不操作"。在这种情况下,只需创建一对反向迭代器就可以"反转"字符串,而不是实际移动存储中的任何字符,而是创建一个视图,通过该视图可以反向迭代字符串。
- @你说得比我好。
- 但是通过这种计算,我们可以说任何计算都只是你如何解释输入数据的问题,因此所有的计算都有O(0)的复杂性,不需要定义计算复杂性。
- @只有在以所需方式重新解释数据没有额外计算成本的情况下,DigiProc才会这样做,例如反向迭代字符串。