时间复杂度是 O(n) 还是 O(n^2)?
Is Time complexity O(n) or O(n^2)?
感觉自己写的这个js函数的时间复杂度是O(n),但同时又感觉是O(n^2)。正确的时间复杂度是多少?该函数应该找到找到的第一个重复项的最后一个索引。例如,在第一个示例中,在索引 0 处找到了 1,在索引 6 处也找到了 1。因此结果将为 6,因为这是该数组中第一个重复值的最后一个索引。如果没有找到重复项,那么我们 return -1.
// [1, 2, 4, 5, 2, 3, 1] --> output: 6
// [1, 1, 3, 2, 4] --> output: 1
// [1, 2, 3, 4, 5, 6] --> output: -1(not found)
// It can be in any order, just need to find the last index of the first duplicate value thats there
const findFirstDuplicateIndex = (arr) => {
let ptr1 = 0
let ptr2 = 1
while (ptr1 < arr.length - 1) { // O(n)
if(arr[ptr1] === arr[ptr2]) {
return ptr2 + 1
} else {
ptr2++
}
if (ptr2 === arr.length - 1) {ptr1++; ptr2 = ptr1 + 1}
}
return -1
}
在时间复杂度概念中,我们必须为循环声明分配一个 1 值,我们必须检查条件,然后我们必须检查它将 运行 和 return 的次数我们必须加 1 的任何值,完成后我们必须添加 alll .
你的代码的时间复杂度是O(n^2).
您的代码是两个嵌套循环的另一个版本。
if (ptr2 === arr.length - 1) {ptr1++; ptr2 = ptr1 + 1}
这段代码相当于添加了一个嵌套循环,即
for(ptr2 = ptr1+1; ptr2 < arr.length; ; ++ptr2)
如果我们用两个嵌套的 for 循环重写您的代码,我们有
const findFirstDuplicateIndex = (arr) => {
for(let ptr1=0; ptr1 < arr.length - 1; ++ptr1) {
for(let ptr2=ptr1+1; ptr2 < arr.length; ++ptr2){
if(arr[ptr1] === arr[ptr2]) {
return ptr2
}
}
}
return -1;
}
现在,
For the 1st iteration: the inner loop will cost N-1.
For the 2nd iteration: the inner loop will cost N-2.
For the 3rd iteration: the inner loop will cost N-3.
........................
........................
For the (N-1)th iteration: the inner loop will cost 1.
因此,总时间复杂度是成本的总和
(N-1) + (N-2) + (N-3) + . . . . + 1
这是一个等差级数,使用算术和公式我们有
(N-1) + (N-2) + (N-3) + . . . . + 1 = (N-1)*(N-2)/2 = O(N^2)
因此,你的代码的时间复杂度是O(n^2).
不管你怎么写,最终。您的代码 运行 来自 0..n-2
的变量 arr1
和来自 arr1+1..n-1
的每个 arr1
的变量 arr2
。这会产生 O(N^2)
(最坏情况)时间复杂度。
但是由于更复杂的算法不容易被发现,一个好的
评估复杂性的方法是使用算法的检测版本,您只需计算步骤数即可。并使用病理学上最坏情况的数据作为输入。
在您的情况下,最坏的情况是,当重复项位于数组中的最后 2 个值时(例如 [5 4 3 2 1 1]
)。
因此,在第 1 步中,为自己编写一些测试数据生成器:
(defun gen-test-data (n)
(make-array (+ n 1)
:initial-contents
(append
(loop for x from n downto 1
collecting x)
'(1))))
它在长度为 (n+1) 的数组中生成病态模式。
(gen-test-data 20)
#(20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 1)
接下来,编写您的检测算法,其中 returns 一些额外信息:
(defun first-duplicate (data)
(let* ((arr (etypecase data
((simple-vector *) data)
(cons (make-array (length data)
:initial-contents data))))
(n (array-dimension arr 0)))
(loop
with counter = 0
for i0 below (- n 1)
do (loop
for i1 from (+ i0 1) below n
do (when (= (aref arr i0) (aref arr i1))
(return-from first-duplicate
(list :value (aref arr i0)
:i0 i0
:i1 i1
:counter counter
:n n)))
do (incf counter)))))
我在这里选择了嵌套循环符号,因为无论如何你都会这样做。
现在的最后一步是 运行 各种 n 的函数,因此您可以看到 n 与 counter
的关系:
(loop for n from 2 to 100 by 10
collecting (first-duplicate (gen-test-data n)))
((:VALUE 1 :I0 1 :I1 2 :COUNTER 2 :N 3)
(:VALUE 1 :I0 11 :I1 12 :COUNTER 77 :N 13)
(:VALUE 1 :I0 21 :I1 22 :COUNTER 252 :N 23)
(:VALUE 1 :I0 31 :I1 32 :COUNTER 527 :N 33)
(:VALUE 1 :I0 41 :I1 42 :COUNTER 902 :N 43)
(:VALUE 1 :I0 51 :I1 52 :COUNTER 1377 :N 53)
(:VALUE 1 :I0 61 :I1 62 :COUNTER 1952 :N 63)
(:VALUE 1 :I0 71 :I1 72 :COUNTER 2627 :N 73)
(:VALUE 1 :I0 81 :I1 82 :COUNTER 3402 :N 83)
(:VALUE 1 :I0 91 :I1 92 :COUNTER 4277 :N 93))
输出现在清楚地表明,它不能是 O(N)
,而是 O(N^2)
。
感觉自己写的这个js函数的时间复杂度是O(n),但同时又感觉是O(n^2)。正确的时间复杂度是多少?该函数应该找到找到的第一个重复项的最后一个索引。例如,在第一个示例中,在索引 0 处找到了 1,在索引 6 处也找到了 1。因此结果将为 6,因为这是该数组中第一个重复值的最后一个索引。如果没有找到重复项,那么我们 return -1.
// [1, 2, 4, 5, 2, 3, 1] --> output: 6
// [1, 1, 3, 2, 4] --> output: 1
// [1, 2, 3, 4, 5, 6] --> output: -1(not found)
// It can be in any order, just need to find the last index of the first duplicate value thats there
const findFirstDuplicateIndex = (arr) => {
let ptr1 = 0
let ptr2 = 1
while (ptr1 < arr.length - 1) { // O(n)
if(arr[ptr1] === arr[ptr2]) {
return ptr2 + 1
} else {
ptr2++
}
if (ptr2 === arr.length - 1) {ptr1++; ptr2 = ptr1 + 1}
}
return -1
}
在时间复杂度概念中,我们必须为循环声明分配一个 1 值,我们必须检查条件,然后我们必须检查它将 运行 和 return 的次数我们必须加 1 的任何值,完成后我们必须添加 alll .
你的代码的时间复杂度是O(n^2).
您的代码是两个嵌套循环的另一个版本。
if (ptr2 === arr.length - 1) {ptr1++; ptr2 = ptr1 + 1}
这段代码相当于添加了一个嵌套循环,即
for(ptr2 = ptr1+1; ptr2 < arr.length; ; ++ptr2)
如果我们用两个嵌套的 for 循环重写您的代码,我们有
const findFirstDuplicateIndex = (arr) => {
for(let ptr1=0; ptr1 < arr.length - 1; ++ptr1) {
for(let ptr2=ptr1+1; ptr2 < arr.length; ++ptr2){
if(arr[ptr1] === arr[ptr2]) {
return ptr2
}
}
}
return -1;
}
现在,
For the 1st iteration: the inner loop will cost N-1.
For the 2nd iteration: the inner loop will cost N-2.
For the 3rd iteration: the inner loop will cost N-3.
........................
........................
For the (N-1)th iteration: the inner loop will cost 1.
因此,总时间复杂度是成本的总和
(N-1) + (N-2) + (N-3) + . . . . + 1
这是一个等差级数,使用算术和公式我们有
(N-1) + (N-2) + (N-3) + . . . . + 1 = (N-1)*(N-2)/2 = O(N^2)
因此,你的代码的时间复杂度是O(n^2).
不管你怎么写,最终。您的代码 运行 来自 0..n-2
的变量 arr1
和来自 arr1+1..n-1
的每个 arr1
的变量 arr2
。这会产生 O(N^2)
(最坏情况)时间复杂度。
但是由于更复杂的算法不容易被发现,一个好的 评估复杂性的方法是使用算法的检测版本,您只需计算步骤数即可。并使用病理学上最坏情况的数据作为输入。
在您的情况下,最坏的情况是,当重复项位于数组中的最后 2 个值时(例如 [5 4 3 2 1 1]
)。
因此,在第 1 步中,为自己编写一些测试数据生成器:
(defun gen-test-data (n)
(make-array (+ n 1)
:initial-contents
(append
(loop for x from n downto 1
collecting x)
'(1))))
它在长度为 (n+1) 的数组中生成病态模式。
(gen-test-data 20)
#(20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 1)
接下来,编写您的检测算法,其中 returns 一些额外信息:
(defun first-duplicate (data)
(let* ((arr (etypecase data
((simple-vector *) data)
(cons (make-array (length data)
:initial-contents data))))
(n (array-dimension arr 0)))
(loop
with counter = 0
for i0 below (- n 1)
do (loop
for i1 from (+ i0 1) below n
do (when (= (aref arr i0) (aref arr i1))
(return-from first-duplicate
(list :value (aref arr i0)
:i0 i0
:i1 i1
:counter counter
:n n)))
do (incf counter)))))
我在这里选择了嵌套循环符号,因为无论如何你都会这样做。
现在的最后一步是 运行 各种 n 的函数,因此您可以看到 n 与 counter
的关系:
(loop for n from 2 to 100 by 10 collecting (first-duplicate (gen-test-data n)))
((:VALUE 1 :I0 1 :I1 2 :COUNTER 2 :N 3)
(:VALUE 1 :I0 11 :I1 12 :COUNTER 77 :N 13)
(:VALUE 1 :I0 21 :I1 22 :COUNTER 252 :N 23)
(:VALUE 1 :I0 31 :I1 32 :COUNTER 527 :N 33)
(:VALUE 1 :I0 41 :I1 42 :COUNTER 902 :N 43)
(:VALUE 1 :I0 51 :I1 52 :COUNTER 1377 :N 53)
(:VALUE 1 :I0 61 :I1 62 :COUNTER 1952 :N 63)
(:VALUE 1 :I0 71 :I1 72 :COUNTER 2627 :N 73)
(:VALUE 1 :I0 81 :I1 82 :COUNTER 3402 :N 83)
(:VALUE 1 :I0 91 :I1 92 :COUNTER 4277 :N 93))
输出现在清楚地表明,它不能是 O(N)
,而是 O(N^2)
。