簡體   English   中英

Python list.pop(i) 時間復雜度?

[英]Python list.pop(i) time complexity?

我在網上list.pop()一下,知道list.pop()時間復雜度為 O(1) 但list.pop(i) ) 的時間復雜度為 O(n) 。 當我在編寫 leetcode 時,很多人在 for 循環中使用pop(i) ,他們說它的時間復雜度為 O(n),實際上它比我的代碼快,我的代碼只使用一個循環,但該循環中有很多行。 我想知道為什么會發生這種情況,我應該使用pop(i)而不是多行來避免它嗎?

示例:Leetcode 26. 從排序數組中刪除重復項

我的代碼:(比 75% 快)

class Solution(object):
    def removeDuplicates(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        left, right = 0, 0
        count = 1
        while right < len(nums)-1:
            if nums[right] == nums[right+1]:
                right += 1
            else:
                nums[left+1]=nums[right+1]
                left += 1
                right += 1
                count += 1
        return count

和其他人的代碼,比 90% 快:(這家伙不說 O(n),但為什么 O(n^2) 比我的 O(n) 快?)

https://leetcode.com/problems/remove-duplicates-from-sorted-array/discuss/477370/python-3%3A-straight-forward-6-lines-solution-90-faster-100-less-memory

我的優化代碼(快於 89%)

class Solution(object):
    def removeDuplicates(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        left, right = 0, 0
        while right < len(nums)-1:
            if nums[right] != nums[right+1]:
                nums[left+1]=nums[right+1]
                left += 1
            right += 1
        return left + 1

您的算法確實需要 O(n) 時間,而“逆序彈出”算法確實需要 O(n²) 時間。 然而,LeetCode 並未報告您的時間復雜度優於 89% 的提交; 它報告您的實際運行時間優於所有提交的 89%。 實際運行時間取決於測試算法的輸入; 不僅是大小,還有重復的數量。

它還取決於如何平均跨多個測試用例的運行時間; 如果大多數測試用例是針對二次解更快的小輸入,那么即使二次解的時間復雜度更高,它也可能整體領先。 @Heap Overflow 在評論中還指出,LeetCode 判斷系統的開銷時間與算法運行所需的時間成比例地大且變化很大,因此差異可能只是由於該開銷的隨機變化造成的。

為了闡明這一點,我使用timeit測量了運行時間。 下圖顯示了我的結果; 考慮到時間復雜性,這些形狀正是您所期望的,並且交叉點在我的機器上介於8000 < n < 9000之間。 這是基於排序列表,其中每個不同的元素平均出現兩次。 我用來生成時間的代碼如下。

運行次數

計時碼:

def linear_solution(nums):
    left, right = 0, 0
    while right < len(nums)-1:
        if nums[right] != nums[right+1]:
            nums[left+1]=nums[right+1]
            left += 1
        right += 1
    return left + 1

def quadratic_solution(nums):
    prev_obj = []
    for i in range(len(nums)-1,-1,-1):
        if prev_obj == nums[i]:
            nums.pop(i)
        prev_obj = nums[i]
    return len(nums)

from random import randint
from timeit import timeit

def gen_list(n):
    max_n = n // 2
    return sorted(randint(0, max_n) for i in range(n))

# I used a step size of 1000 up to 15000, then a step size of 5000 up to 50000
step = 1000
max_n = 15000
reps = 100

print('n', 'linear time (ms)', 'quadratic time (ms)', sep='\t')
for n in range(step, max_n+1, step):
    # generate input lists
    lsts1 = [ gen_list(n) for i in range(reps) ]
    # copy the lists by value, since the algorithms will mutate them
    lsts2 = [ list(g) for g in lsts1 ]
    # use iterators to supply the input lists one-by-one to timeit
    iter1 = iter(lsts1)
    iter2 = iter(lsts2)
    t1 = timeit(lambda: linear_solution(next(iter1)), number=reps)
    t2 = timeit(lambda: quadratic_solution(next(iter2)), number=reps)
    # timeit reports the total time in seconds across all reps
    print(n, 1000*t1/reps, 1000*t2/reps, sep='\t')

結論是,對於足夠大的輸入,您的算法確實比二次解法快,但是 LeetCode 用來測量運行時間的輸入“不夠大”,無法克服判斷開銷的變化,而且平均值包括在二次算法更快的較小輸入上測量的時間。

僅僅因為解決方案不是 O(n),你不能假設它是 O(n^2)。

它並沒有完全變成 O(n^2) 因為他以相反的順序使用 pop 減少了每次彈出的時間,在正向順序上使用 pop(i) 將比反向使用更多的時間,因為 pop 搜索從反向開始,在每個循環中,他都在減少背面的元素數量。 以非相反的順序嘗試相同的解決方案,運行幾次以確保,你會看到。

無論如何,關於為什么他的解決方案更快,你有一個包含很多變量的 if 條件,他只使用了一個變量prev_obj ,使用相反的順序可以只使用一個變量。 因此,在您的情況下,基本數學運算的數量更多,因此在 O(n) 復雜度相同的情況下,您的每個 n 循環都比他的長。

看看你的計數變量,在每次迭代中,它的值是left+1你可以返回left+1 ,只需刪除它就會減少你必須做的 n 數量count=count+1

我剛剛發布了這個解決方案,速度提高了 76%

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        a=sorted(set(nums),key=lambda item:item)
        for i,v in enumerate(a):
            nums[i]=v
        return len(a)

而這個速度超過 90%

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        a ={k:1 for k in nums} #<--- this is O(n)
        for i,v in enumerate(a.keys()): #<--- this is another O(n), but the length is small so O(m)
            nums[i]=v
        return len(a)

如果您查看 for 循環,您可以說它們都超過 O(n),但是因為當我循環遍歷減少的成員而您的代碼循環遍歷所有成員時,我們正在使用重復成員。 因此,制作該唯一 set/dict 所需的時間如果少於您遍歷這些額外成員並檢查 if 條件所需的時間,那么我的解決方案可以更快。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM