嵌套的 while 循环怎么可能是 O(n)?
How can a nested while loop be O(n)?
我从网站 www.interviewcake.com 那里获得了这段代码,该网站说它是 O(n),并且在采访 cake 时要求团队成员澄清,他们证实这是真的。这是代码:
function reverseWords(message) {
// First we reverse all the characters in the entire message
reverseCharacters(message, 0, message.length - 1);
// This gives us the right word order
// but with each word backward
// Now we'll make the words forward again
// by reversing each word's characters
// We hold the index of the *start* of the current word
// as we look for the *end* of the current word
let currentWordStartIndex = 0;
for (let i = 0; i <= message.length; i++) {
// Found the end of the current word!
if (i === message.length || message[i] === ' ') {
// If we haven't exhausted the string our
// next word's start is one character ahead
reverseCharacters(message, currentWordStartIndex, i - 1);
currentWordStartIndex = i + 1;
}
}
}
function reverseCharacters(message, leftIndex, rightIndex) {
// Walk towards the middle, from both sides
while (leftIndex < rightIndex) {
// Swap the left char and right char
const temp = message[leftIndex];
message[leftIndex] = message[rightIndex];
message[rightIndex] = temp;
leftIndex++;
rightIndex--;
}
}
面试cake的组员是这样解释的:
*是的,有一个嵌套循环让它看起来像是 O(n^2)。
但是,在整个问题过程中,我们可以看到我们没有在每个内部循环中反转 O( n ) 个字符……我们只将每个字符正好反转两次。因此,总成本最终为 O(n)。
这是一个棘手的问题,因为您必须查看整个算法中对 reverseCharacters 的所有调用的成本,而不是每次调用本身的成本。*
不过我仍然很困惑,因为我们仍在内部循环中循环遍历每个字符,字符串越大,调用 运行 所需的时间就越长。
我想将它打开到一个不同的渠道,看看我是否可以获得一些额外的见解,以及为什么它的 O(n) 而不是 O(n)^2。
为了完全清楚,我想解释为什么上面提供的代码中的 reverseWords
函数是 O(n) 而不是 O(n)^2
据我在该代码中所知,外循环的工作是查找每个单词的结尾。一旦找到一个(并且只有在那时),就会调用 reverseCharacters()
来反转该单词中的字符。所以两个任务的大O加在一起(O(n)+ O(n)= O(2n),仍然认为是O(n)),而不是相乘(O(n)* O (n) = O(n^2)) 正如您通常期望它们在嵌套循环中所做的那样。
这确实是O(n)
。
混淆来自于 reverseCharacters()
在内循环中调用的事实。但是,请注意何时调用它。
让我们看一下字符串中的任意字符,看看它出现的频率 "touched"。
对于某些 i
.
,让我们的角色位于索引 i
reverseCharacters()
在内循环中,输入字符串中的每个单词被调用一次。字符 i
正好出现在一个单词中。
这意味着,这段代码的实际复杂度是:
T(n) = O(n // First reverseCharacters()
+ n // looping through all characters
+ l1 // reverseCharacters() on the first word
+ l2 // reverseCharacters() on the second word
+ ... // and so on ...
+ lk) // reverseCharacters() on the k'th (and last) word.
因为 l1 + l2 + ... + lk <= n
,这意味着,我们有:
T(n) = O(n + n + l1 + l2 + ... + lk) <= O(n + n + n) = O(n)
我从网站 www.interviewcake.com 那里获得了这段代码,该网站说它是 O(n),并且在采访 cake 时要求团队成员澄清,他们证实这是真的。这是代码:
function reverseWords(message) {
// First we reverse all the characters in the entire message
reverseCharacters(message, 0, message.length - 1);
// This gives us the right word order
// but with each word backward
// Now we'll make the words forward again
// by reversing each word's characters
// We hold the index of the *start* of the current word
// as we look for the *end* of the current word
let currentWordStartIndex = 0;
for (let i = 0; i <= message.length; i++) {
// Found the end of the current word!
if (i === message.length || message[i] === ' ') {
// If we haven't exhausted the string our
// next word's start is one character ahead
reverseCharacters(message, currentWordStartIndex, i - 1);
currentWordStartIndex = i + 1;
}
}
}
function reverseCharacters(message, leftIndex, rightIndex) {
// Walk towards the middle, from both sides
while (leftIndex < rightIndex) {
// Swap the left char and right char
const temp = message[leftIndex];
message[leftIndex] = message[rightIndex];
message[rightIndex] = temp;
leftIndex++;
rightIndex--;
}
}
面试cake的组员是这样解释的:
*是的,有一个嵌套循环让它看起来像是 O(n^2)。
但是,在整个问题过程中,我们可以看到我们没有在每个内部循环中反转 O( n ) 个字符……我们只将每个字符正好反转两次。因此,总成本最终为 O(n)。
这是一个棘手的问题,因为您必须查看整个算法中对 reverseCharacters 的所有调用的成本,而不是每次调用本身的成本。*
不过我仍然很困惑,因为我们仍在内部循环中循环遍历每个字符,字符串越大,调用 运行 所需的时间就越长。
我想将它打开到一个不同的渠道,看看我是否可以获得一些额外的见解,以及为什么它的 O(n) 而不是 O(n)^2。
为了完全清楚,我想解释为什么上面提供的代码中的 reverseWords
函数是 O(n) 而不是 O(n)^2
据我在该代码中所知,外循环的工作是查找每个单词的结尾。一旦找到一个(并且只有在那时),就会调用 reverseCharacters()
来反转该单词中的字符。所以两个任务的大O加在一起(O(n)+ O(n)= O(2n),仍然认为是O(n)),而不是相乘(O(n)* O (n) = O(n^2)) 正如您通常期望它们在嵌套循环中所做的那样。
这确实是O(n)
。
混淆来自于 reverseCharacters()
在内循环中调用的事实。但是,请注意何时调用它。
让我们看一下字符串中的任意字符,看看它出现的频率 "touched"。
对于某些 i
.
i
reverseCharacters()
在内循环中,输入字符串中的每个单词被调用一次。字符 i
正好出现在一个单词中。
这意味着,这段代码的实际复杂度是:
T(n) = O(n // First reverseCharacters()
+ n // looping through all characters
+ l1 // reverseCharacters() on the first word
+ l2 // reverseCharacters() on the second word
+ ... // and so on ...
+ lk) // reverseCharacters() on the k'th (and last) word.
因为 l1 + l2 + ... + lk <= n
,这意味着,我们有:
T(n) = O(n + n + l1 + l2 + ... + lk) <= O(n + n + n) = O(n)