【问题标题】:Is splitting an array into 2 subarrays and solving them recursively still O(log(n))?是否将数组拆分为 2 个子数组并递归求解它们仍然 O(log(n))?
【发布时间】:2019-08-11 12:10:08
【问题描述】:

我在https://www.geeksforgeeks.org/median-of-two-sorted-arrays/ 上找到了计算 2 个排序列表中位数的算法。 它说,它是 O(log(n))。 但真的是这样吗?

我感到困惑的是: 这些行将一个数组拆分为 2 个子数组(使用 Python 的切片)并递归求解:

if n % 2 == 0: 
   return getMedian(arr1[:int(n / 2) + 1], 
      arr2[int(n / 2) - 1:], int(n / 2) + 1) 
else: 
   return getMedian(arr1[:int(n / 2) + 1],  
      arr2[int(n / 2):], int(n / 2) + 1) 

但对我来说,拆分数组看起来像 O(n)。 所以在我看来,整个算法一定是O(n * log n)...

在这里,您可以看到我正在谈论的算法的整个代码:

# using divide and conquer we divide 
# the 2 arrays accordingly recursively 
# till we get two elements in each  
# array, hence then we calculate median 

#condition len(arr1)=len(arr2)=n 
def getMedian(arr1, arr2, n):  

    # there is no element in any array 
    if n == 0:  
        return -1

    # 1 element in each => median of  
    # sorted arr made of two arrays will     
    elif n == 1:  
        # be sum of both elements by 2 
        return (arr1[0]+arr2[1])/2

    # Eg. [1,4] , [6,10] => [1, 4, 6, 10] 
    # median = (6+4)/2     
    elif n == 2:  
        # which implies median = (max(arr1[0], 
        # arr2[0])+min(arr1[1],arr2[1]))/2 
        return (max(arr1[0], arr2[0]) + 
                min(arr1[1], arr2[1])) / 2

    else: 
        #calculating medians      
        m1 = median(arr1, n) 
        m2 = median(arr2, n) 

        # then the elements at median  
        # position must be between the  
        # greater median and the first  
        # element of respective array and  
        # between the other median and  
        # the last element in its respective array. 
        if m1 > m2: 

            if n % 2 == 0: 
                return getMedian(arr1[:int(n / 2) + 1], 
                        arr2[int(n / 2) - 1:], int(n / 2) + 1) 
            else: 
                return getMedian(arr1[:int(n / 2) + 1],  
                        arr2[int(n / 2):], int(n / 2) + 1) 

        else: 
            if n % 2 == 0: 
                return getMedian(arr1[int(n / 2 - 1):], 
                        arr2[:int(n / 2 + 1)], int(n / 2) + 1) 
            else: 
                return getMedian(arr1[int(n / 2):],  
                        arr2[0:int(n / 2) + 1], int(n / 2) + 1) 

 # function to find median of array 
def median(arr, n): 
    if n % 2 == 0: 
        return (arr[int(n / 2)] +
                arr[int(n / 2) - 1]) / 2
    else: 
        return arr[int(n/2)] 


# Driver code 
arr1 = [1, 2, 3, 6] 
arr2 = [4, 6, 8, 10] 
n = len(arr1) 
print(int(getMedian(arr1,arr2,n))) 

# This code is contributed by 
# baby_gog9800 

【问题讨论】:

  • 在 Python 中,如果你对一个列表进行切片,你会创建一个副本,因此这将花费 O(n)。 GFG 文章中的切片不会创建副本,它只是保存列表如何切片的指针。
  • 它将两个数组分成两半,所以从 2n 大小你去 n/2+n/2=n 所以你把问题减半了。虽然如果它只是一个大小为 n 的数组,那么执行 n/2+n/2=n 仍然是 O(n)。

标签: algorithm runtime time-complexity


【解决方案1】:

是的,当然。许多候选人因为错过了这一点而在编程面试中得到了不好的分数。

在 python 中对列表进行切片会生成副本。

复制一半列表需要 O(n) 时间。

而且这个算法总共花费了 O(n) 时间(你应该去弄清楚为什么它不是 O(n log n)

你真的需要知道你的语言是如何工作的,才能为任何特定的例子弄清楚这一点,因为有些语言提供了在不复制元素的情况下对列表进行切片的方法。在 java 中,您可以调用 list.sublist(start,end),例如,在不复制的情况下获取切片。

【讨论】:

  • 在 Java 的情况下,它是库,而不是语言。接口方法List.sublist() 的约定是它返回原始列表的视图,在没有通过视图以外的结构更改的情况下有效。这使得 O(1) 实现可行且可能,就像在 java.util.ArrayList 中一样——但不能保证。
  • 好的。在每次调用 getMedian 时,我们只调用一次 getMedian(如果 n >= 2)。所以我可以通过主定理论证 f(n) = f(n/2) + Θ(n)。 (1/2)^1
【解决方案2】:

这里的问题是您混淆了算法实现。这个 Python 实现O(n),因为它执行线性时间切片操作,但算法本身是O(log(n)),因为它实际上不需要执行线性复制切片中元素的时间操作——它可以对同一个列表进行操作,而无需创建新列表。这使得f(n) = O(1) 在主定理中,使得算法的整体运行时间O(log n)。您可以选择以不需要切片的方式在 Python 中实现算法(例如,类似于 GeeksForGeeks 上的 C++ 和 Java 实现),它将在O(log n) 时间运行。

算法与其实现之间的区别在于为什么算法分析是在伪代码上执行的,而不是在实际编程语言中的实现。像这样的实现细节经常会引起混淆。因此,算法往往会根据其要求明确说明所使用的操作及其时间复杂性(如索引、切片等)。

【讨论】:

  • 所以我没有进行切片,而是一直省略整个数组和另外 2 个参数,如 lr(用于左和右)告诉我我正在处理哪个间隔在那个递归调用中?
  • @Alpha 没错!这样做也会使 Python 实现 O(log n)
  • 这个python代码中实现的算法是O(n)。不同语言的类似代码可能会实现 O(log n) 算法。
  • @MattTimmermans 您也混淆了算法的实现 :) 再次强调您的陈述,该算法在此python代码中实现是 O(n)。算法是明确的——除其他外,它们不会“改变”它们的时间复杂度。在这种情况下,固有算法是O(log n),并将始终保持O(log n)。然而,给定的 Python 实现在 O(n) 时间运行。不同语言的类似代码将实现 same 算法,因为代码解决问题的方式(算法)保持不变。
  • 不,你混淆了作者为他应该实现的算法而实现的算法。
猜你喜欢
  • 2018-01-09
  • 1970-01-01
  • 2018-02-09
  • 2020-09-15
  • 2021-09-29
  • 1970-01-01
  • 1970-01-01
  • 2012-06-21
  • 1970-01-01
相关资源
最近更新 更多