Javascript 这个用于删除重复项的递归解决方案的时间复杂度是多少?

Javascript 这个用于删除重复项的递归解决方案的时间复杂度是多少?,javascript,algorithm,recursion,duplicates,Javascript,Algorithm,Recursion,Duplicates,在我完成一个Leetcode问题之后,我总是试图确定渐近时间复杂度,以便于实践 我现在看问题: 给定一个已排序的数组nums,在适当的位置删除重复项,以便 每个元素只显示一次并返回新的长度 不要为另一个数组分配额外空间,必须通过 使用O1额外内存就地修改输入阵列 澄清: 不明白为什么返回值是整数,而您的答案是整数 阵列 请注意,输入数组是通过引用传入的,这意味着 调用者也会知道对输入数组的修改 在内部,你可以这样想: // nums is passed in by reference. (i.e

在我完成一个Leetcode问题之后,我总是试图确定渐近时间复杂度,以便于实践

我现在看问题:

给定一个已排序的数组nums,在适当的位置删除重复项,以便 每个元素只显示一次并返回新的长度

不要为另一个数组分配额外空间,必须通过 使用O1额外内存就地修改输入阵列

澄清: 不明白为什么返回值是整数,而您的答案是整数 阵列

请注意,输入数组是通过引用传入的,这意味着 调用者也会知道对输入数组的修改

在内部,你可以这样想:

// nums is passed in by reference. (i.e., without making a copy) int
len = removeDuplicates(nums);

// any modification to nums in your function would be known by the caller. 
// using the length returned by your function, it prints the first len elements.
for (int i = 0; i < len; i++) {
    print(nums[i]);
}
对于这个问题,我从研究中得到了答案。每次运行时,执行时间减半。有人能核实或确定我是否错了吗

所有递归函数都是天生的Ologn吗?即使有多个循环

对于这个问题,我从研究中得到了答案。每次运行时,执行时间减半。有人能核实或确定我是否错了吗

每次运行的执行时间不会减半:想象一个极端情况,输入有100个值,它们都是相同的。然后,在递归树的每一级,将找到并删除其中一个重复项。然后进行更深层次的递归调用。因此,对于每个重复值,递归树中都有一个级别。在这种极端情况下,递归树的深度为99

即使要修改算法,也不可能将其设置为Olog n,因为数组中的所有值都需要至少读取一次,仅此一项就已经使其时间复杂度为On

您的实现使用的拼接需要移动删除点后面的所有值,因此一个拼接已经打开,这使得您的算法在最坏的情况下运行

由于递归,在最坏的情况下,它还为调用堆栈使用额外的空间

所有递归函数都是天生的Ologn吗

不,使用递归并不能说明总体时间复杂性。它可能是任何东西。在进行递归调用时,如果可以忽略当前数组的一半,则通常会得到Ologn。例如,二进制搜索算法就是这样

改善 通过不使用递归,而是使用迭代方法,可以避免额外的空间。此外,不需要实际更改给定数组的长度,只需返回其新长度。因此,您可以避免使用拼接。相反,在数组中使用两个索引:一个运行到下一个不同字符的索引,另一个运行较慢的索引,将新字符复制到该索引。当较快的索引到达输入端时,较慢的索引指示具有唯一值的零件的大小

这看起来是这样的:

var removeDuplicates = function(nums) {
    if (nums.length == 0) return 0;
    let len = 1;
    for (let j = 1; j < nums.length; j++) {
        if (nums[j-1] !== nums[j]) nums[len++] = nums[j];
    }
    return len;
};
对于这个问题,我从研究中得到了答案。每次运行时,执行时间减半。有人能核实或确定我是否错了吗

每次运行的执行时间不会减半:想象一个极端情况,输入有100个值,它们都是相同的。然后,在递归树的每一级,将找到并删除其中一个重复项。然后进行更深层次的递归调用。因此,对于每个重复值,递归树中都有一个级别。在这种极端情况下,递归树的深度为99

即使要修改算法,也不可能将其设置为Olog n,因为数组中的所有值都需要至少读取一次,仅此一项就已经使其时间复杂度为On

您的实现使用的拼接需要移动删除点后面的所有值,因此一个拼接已经打开,这使得您的算法在最坏的情况下运行

由于递归,在最坏的情况下,它还为调用堆栈使用额外的空间

所有递归函数都是天生的Ologn吗

不,使用递归并不能说明总体时间复杂性。它可能是任何东西。在进行递归调用时,如果可以忽略当前数组的一半,则通常会得到Ologn。例如,二进制搜索算法就是这样

改善 通过不使用递归,而是使用迭代方法,可以避免额外的空间。此外,不需要实际更改给定数组的长度,只需返回其新长度。因此,您可以避免使用拼接。相反,在数组中使用两个索引:一个运行到下一个不同字符的索引,另一个运行较慢的索引,将新字符复制到该索引。当较快的索引到达输入端时,较慢的索引指示具有唯一值的零件的大小

这看起来是这样的:

var removeDuplicates = function(nums) {
    if (nums.length == 0) return 0;
    let len = 1;
    for (let j = 1; j < nums.length; j++) {
        if (nums[j-1] !== nums[j]) nums[len++] = nums[j];
    }
    return len;
};

数组中是否需要文本null?为什么是递归的?我看不出他有什么好处
这可能是启用的,但是您的解决方案更糟糕,因为它会重新检查递归调用中的所有元素。所有递归函数是否都固有的Olong?即使存在多个循环,假设您的意思是log n-not。并非所有递归函数都具有这种性能特征。len=list,count=0=>list.length==0?count:lencount.slice1,count+1将仅启用,不能是其他任何内容。一些递归函数将是OLOGN,如树遍历或二进制搜索。然而,有些算法会有更高的复杂度,比如寻找所有的排列或斐波那契数。我认为你的算法是在最坏的情况下,在最好的情况下,平均情况下,介于两者之间。要回答您的问题,您是否希望数组中的文本为空?为什么是递归的?我看不出这有什么好处。这可能是启用的,但是您的解决方案更糟糕,因为它会重新检查递归调用中的所有元素。所有递归函数是否都固有地为ONG?即使存在多个循环,假设您的意思是log n-not。并非所有递归函数都具有这种性能特征。len=list,count=0=>list.length==0?count:lencount.slice1,count+1将仅启用,不能是其他任何内容。一些递归函数将是OLOGN,如树遍历或二进制搜索。然而,有些算法会有更高的复杂度,比如寻找所有的排列或斐波那契数。我认为你的算法是在最坏的情况下,在最好的情况下,平均情况下,介于两者之间。如果您不想修改数组,要真正回答您的问题,您应该编写len++,而不是nums[len++]=nums[j];。如果确实要就地筛选数组,还需要在末尾按nums删除其余元素。length=len;在循环之后。OP应该添加Leetcode问题的规范,我相信这是正确的。它需要就地修改,并返回新的长度,而无需缩短输入数组。此解决方案通过了Leetcode的测试。超出返回长度的内容无关紧要。-哦,哇,太奇怪了。然而,示例输出确实缩短了数组,并且似乎没有一个要求阻止这样做。事实上,这很奇怪。由于Leetcode测量执行时间并给您一个比较分数,Leetcode有一个习惯,即排除任何不需要的内容,例如:调整数组的长度。@Tyrique,这回答了您的问题吗?如果您不想修改数组,您应该编写len++而不是nums[len++]=nums[j];。如果确实要就地筛选数组,还需要在末尾按nums删除其余元素。length=len;在循环之后。OP应该添加Leetcode问题的规范,我相信这是正确的。它需要就地修改,并返回新的长度,而无需缩短输入数组。此解决方案通过了Leetcode的测试。超出返回长度的内容无关紧要。-哦,哇,太奇怪了。然而,示例输出确实缩短了数组,并且似乎没有一个要求阻止这样做。事实上,这很奇怪。由于Leetcode测量执行时间并给您一个比较分数,因此Leetcode有一个习惯,即排除任何不需要的内容,例如:调整数组的长度。@Tyrique,这回答了您的问题吗?