Python LeetCode问题的运行时差异原因(打开锁)

Python LeetCode问题的运行时差异原因(打开锁),python,algorithm,time-complexity,Python,Algorithm,Time Complexity,LeetCod问题链接: (第页) 下面是代码#1从源代码和目标代码执行bfs: from collections import deque class Solution: def openLock(self, deadends: List[str], target: str) -> int: deadend = set(deadends) if target in deadend or '0000' in deadend:

LeetCod问题链接: (第页)

下面是代码#1从源代码和目标代码执行bfs:

from collections import deque

class Solution:
    def openLock(self, deadends: List[str], target: str) -> int:
        deadend = set(deadends)
        
        if target in deadend or '0000' in deadend:
            return -1
        
        if target == '0000':
            return 0
        
        def getNextCombi(combi: str) -> List[str]:
            res = []
            for i in range(4):
                nextCombi1 = combi[:i] + str((int(combi[i:i + 1]) + 1) % 10) + combi[i + 1:]
                nextCombi2 = combi[:i] + str((int(combi[i:i + 1]) - 1) % 10) + combi[i + 1:]
                if nextCombi1 not in deadend:
                    res.append(nextCombi1)
                if nextCombi2 not in deadend:
                    res.append(nextCombi2)
            return res
        
        sourceQueue = deque(['0000'])
        targetQueue = deque([target])
        
        sourceSeen = {'0000': 0}
        targetSeen = {target: 0}
        
        while len(sourceQueue) != 0 and len(targetQueue) != 0:
            sourceCombi = sourceQueue.popleft()
            targetCombi = targetQueue.popleft()
            
            for nextCombi in getNextCombi(sourceCombi):
                if nextCombi not in sourceSeen:
                    sourceSeen[nextCombi] = sourceSeen[sourceCombi] + 1
                    sourceQueue.append(nextCombi)
                    if nextCombi in targetSeen:
                        return sourceSeen[nextCombi] + targetSeen[nextCombi]
            
            for nextCombi in getNextCombi(targetCombi):
                if nextCombi not in targetSeen:
                    targetSeen[nextCombi] = targetSeen[targetCombi] + 1
                    targetQueue.append(nextCombi)
                    if nextCombi in sourceSeen:
                        return sourceSeen[nextCombi] + targetSeen[nextCombi]
        
        return -1
下面是代码#2从源代码执行bfs:

from collections import deque

class Solution:
    def openLock(self, deadends: List[str], target: str) -> int:
        deadend = set(deadends)
        
        if target in deadend or '0000' in deadend:
            return -1
        
        if target == '0000':
            return 0
        
        def getNextCombi(combi: str) -> List[str]:
            res = []
            for i in range(4):
                nextCombi1 = combi[:i] + str((int(combi[i:i + 1]) + 1) % 10) + combi[i + 1:]
                nextCombi2 = combi[:i] + str((int(combi[i:i + 1]) - 1) % 10) + combi[i + 1:]
                if nextCombi1 not in deadend:
                    res.append(nextCombi1)
                if nextCombi2 not in deadend:
                    res.append(nextCombi2)
            return res
        
        sourceQueue = deque(['0000'])
        
        sourceSeen = {'0000': 0}
        
        while len(sourceQueue) != 0:
            sourceCombi = sourceQueue.popleft()
            
            for nextCombi in getNextCombi(sourceCombi):
                if nextCombi not in sourceSeen:
                    sourceSeen[nextCombi] = sourceSeen[sourceCombi] + 1
                    sourceQueue.append(nextCombi)
                    if nextCombi == target:
                        return sourceSeen[nextCombi]
代码#1在LeetCode上给出了大约120毫秒的速度, 代码#2在LeetCode上提供了大约640ms的速度。 我试了好几次,所以我相信算法本身有很大的不同

为什么1比2快得多? 我看不出在时间复杂性方面有什么不同。 是否仅仅因为LeetCode中使用的示例在#1上更快

我对这两种代码的分析:
我认为它们具有相同的时间复杂度O(1),因为在最坏的情况下,它会遍历所有可能的组合,即4^9。我不确定我的时间复杂度,但我认为我做BFS源->目标,做BFS源->满足是正确的。我实现了C++程序来测量两种算法中的运行的操作码和行数。为什么C++,而不是Python?不是因为C++速度,而是因为只有Python C API提供足够的功能来精细化代码跟踪函数,以设置它来测量操作码和行。

下一个C++代码的测量结果(输出)为:

Traced 'algo1': time 3441700 nanoseconds, 1455 lines, 9304 opcodes,
    2365 avg ns/line, 369 avg ns/opcode.
Traced 'algo2': time 18011100 nanoseconds, 22678 lines, 155640 opcodes,
    794 avg ns/line, 115 avg ns/opcode.
使用下一个测试输入(取自LeetCode示例)测量两个算法:

因此,algo1比algo2快得多,algo1只执行9304个操作码,而algo2执行155640个操作码,即16倍多的操作码。它是什么意思?
操作码
——它是Python内部最小的操作,与汇编程序内部的指令相同,但这里的操作码是Python虚拟机内部的指令。尽管某些操作码可能比其他操作码花费更长的时间,但它仍然是一个很好的定量度量(操作码的数量)


这意味着即使您的两个算法都是
O(1)
,每个算法中执行的操作数仍然是非常不同的。还要记住,任何复杂度
O(f(N))
实际上意味着运行时间是
,我建议首先分析这两种算法的时间复杂度。如果您需要帮助,请首先尝试并回答您的问题来描述您的分析。请重复并从开始。首先,您需要关注运行时间(挂钟或CPU时间)或算法复杂性。这不是一回事。其次,您需要分析、分析或以其他方式理解代码,以了解什么会影响您选择的度量。最后,您需要与我们分享,并将您的问题集中在您不了解的细节上。“为什么一个比另一个快”是不成熟的:它试图把你的分析责任转移给任何愿意回复你帖子的人。我不知道为什么我会被评判为我的问题。。。这只是一个简单的问题,为什么运行时是不同的(无论是算法上的,还是仅仅因为Leetcode是如何的,等等)。我认为最坏情况下的时间复杂度只是O(1),因为它是O(4^10),我不确定我的分析,所以为什么不像我那样问呢?@jginso7一般来说,如果两个算法是
O(1)
并不意味着它们有相同的实际运行时间,因为任何复杂度
O(f(N))
实际上意味着时间

deadends = ['0201', '0101', '0102', '1212', '2002'], target = '0202'
         397 function calls in 0.003 seconds

   Ordered by: call count

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      161    0.001    0.000    0.001    0.000 {method 'append' of 'list' objects}
      101    0.000    0.000    0.000    0.000 {method 'append' of 'collections.deque' objects}
       22    0.000    0.000    0.000    0.000 {method 'popleft' of 'collections.deque' objects}
       22    0.000    0.000    0.000    0.000 {built-in method builtins.hasattr}
       22    0.000    0.000    0.000    0.000 {built-in method builtins.len}
       21    0.001    0.000    0.002    0.000 C:\dev\tmp_code\main.py:17(getNextCombi)
        1    0.001    0.001    0.003    0.003 C:\dev\tmp_code\main.py:7(openLock)
        1    0.000    0.000    0.000    0.000 C:\bin\Python39\lib\typing.py:256(inner)
        1    0.000    0.000    0.000    0.000 C:\bin\Python39\lib\cProfile.py:117(__exit__)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
         6261 function calls in 0.055 seconds

   Ordered by: call count

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     3093    0.011    0.000    0.011    0.000 {method 'append' of 'list' objects}
      821    0.003    0.000    0.003    0.000 {method 'append' of 'collections.deque' objects}
      391    0.001    0.000    0.001    0.000 {built-in method builtins.hasattr}
      390    0.021    0.000    0.040    0.000 C:\dev\tmp_code\main.py:17(getNextCombi)
      390    0.001    0.000    0.001    0.000 {method 'popleft' of 'collections.deque' objects}
      390    0.001    0.000    0.001    0.000 {built-in method builtins.len}
        1    0.009    0.009    0.055    0.055 C:\dev\tmp_code\main.py:7(openLock)
        1    0.000    0.000    0.000    0.000 C:\bin\Python39\lib\typing.py:256(inner)
        1    0.000    0.000    0.000    0.000 C:\bin\Python39\lib\cProfile.py:117(__exit__)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}