【问题标题】:Python: Reduce runtime?Python:减少运行时间?
【发布时间】:2020-06-13 10:04:30
【问题描述】:

我最近开始学习 python,并且正在使用 CodeWars 进行训练。任务是返回一个列表[p, p + 4, p + 6, p + 10, p + 12, p + 16],其中所有的都是素数。它们的总和应该高于 sum_limit。对于低值,它正在工作,但在高值(大约 200 万)时,运行时间很高。如何减少运行时间?

from math import sqrt; from itertools import count, islice

def find_primes_sextuplet(sum_limit):
    for x in range(sum_limit):
        if isPrime(x) and isPrime(x+4) and isPrime(x+6) and isPrime(x+10) and isPrime(x+12) and isPrime(x+16):
            possible = [x, x+4, x+6, x+10, x+12, x+16]
            if sum(possible) > sum_limit:
                return possible


def isPrime(n):
    return n > 1 and all(n%i for i in islice(count(2), int(sqrt(n)-1)))


print(find_primes_sextuplet(2000000))

【问题讨论】:

  • 你的时间复杂度很高。事实是这种计算是相当密集的。话虽如此,您可以进行很多优化。例如range(sum_limit) 可以是range(3, sum_limit, 2)。这消除了一半要检查的数字。另外,也许最好先筛选sum_limit 下的素数,然后检查任何 6 个连续素数是否相隔 4、2、4、2、4 的空间。

标签: python runtime


【解决方案1】:

对于n 的非负整数值,您可以使用:

def isPrime(n):
    if n == 1 or n % 2 == 0 or n % 3 == 0:
        return False

    end = int(sqrt(n)+1)
    for start in [5, 7]:
        for k in range(start, end, 6):
            if n % k == 0:
                return False

    return True

不会改变理论复杂度,但会减少实际运行时间。

而且如果把外循环改成for x in range(5, sum_limit),那么也可以去掉初始检查if n == 1 or n % 2 == 0 or n % 3 == 0

【讨论】:

    【解决方案2】:

    有多种方法可以提高代码的运行时间。例如,许多数字被检查为素数,即使它们的总和因此不合格。计算 6 个数字的总和比检查它们是否为素数要快。您可以将总和检查移到素数检查之上,并且仅在它们的总和符合条件时才检查素数。

    为了进一步改善这一点,您可以通过从可能数字的下限开始范围来跳过不会产生合格总和的数字。

    x + x + 4 + x + 6 + x + 10 + x + 12 + x + 16 = 6x + 48
    

    应该高于你的 sum_limit

    6x + 48 >= sum_limit
    x >=(sum_limit - 48) / 6
    

    因此,如果您的范围从 x 开始,您将跳过所有不会导致符合条件的总和的数字。 您还可以通过跳过循环中的偶数(通过 range(y,x,2))来改善运行时间。 进一步改进运行时需要您调整 isPrime 函数。

    【讨论】:

      【解决方案3】:

      这是我关于降低复杂性和运行时间的想法。

      您可以在O(n log log n) 中写一个筛子。这是一个合理的实现:

      def sieve(n):
          grid = [None for _ in range(n+1)]
          i = 2
          while i < n+1:
             if grid[i] is None:
                 grid[i] = True
                 for p in range(2*i, n+1, i):
                     grid[p] = False
             else:
                 i += 1
          return (index for index, b in enumerate(grid) if b)
      

      有6个数字,加到第一个数字上的总数是48。所以第一个数字的最小可能值为(n - 48) / 6。在我的筛子中,我们可以迭代生成器,直到 number 大于该值。

      def get_remaining_sieve(n):
          g = sieve(n)
          current = next(g)
          min_value = (n - 48) / 6
          while current < min_value:
              current = next(g)
          return [current] + list(g)
      

      现在只需遍历长度为 6 的每个切片,并检查间隔是否匹配所需的间隔(4、2、4、2、4)。

      remaining = get_remaining_sieve(n)
      for start in range(len(remaining) - 5):
          slice = remaining[start:start+6]
          differences = [slice[j] - slice[j-1] for j in range(1, 6)]
          if differences == [4, 2, 4, 2, 4]:
              print(slice)
      

      总结

      基于这些原则,我想出了这个解决方案:

      from itertools import dropwhile, islice
      def get_solutions(n):
          grid = [None for _ in range(n+1)]
          i = 2
          while i < n+1:
              if grid[i] is None:
                  grid[i] = True
                  for p in range(2*i, n+1, i):
                      grid[p] = False
              else:
                  i += 1
          sieve = (index for index, b in enumerate(grid) if b)
          min_value = (n - 48) / 6
          reduced_sieve = dropwhile(lambda v: v < min_value, sieve)
          reference_slice = list(islice(reduced_sieve, 6))
          while True:
              try:
                  ref = reference_slice[0]
                  differences = [v - ref for v in reference_slice[1:]]
                  if differences == [4, 6, 10, 12, 16]:
                      yield reference_slice
                  reference_slice = reference_slice[1:] + [next(reduced_sieve)]
              except StopIteration:
                  break
      
      
      n = 2000000
      print(next(get_solutions(n)))  # 695ms
      
      # or for all solutions
      for solution in get_solutions(n):  # 755ms
          print(solution)
      

      这在我的电脑上运行不到一秒钟。

      【讨论】:

      • 空间复杂度为O(n/log n)?时间复杂度为n log log n + (n / log n)?
      猜你喜欢
      • 1970-01-01
      • 2019-10-07
      • 1970-01-01
      • 2015-06-02
      • 2018-10-11
      • 1970-01-01
      • 1970-01-01
      • 2014-05-15
      • 2020-08-11
      相关资源
      最近更新 更多