【问题标题】:Optimizing Python 3n + 1 Programming Challenge优化 Python 3n + 1 编程挑战
【发布时间】:2019-02-09 17:57:20
【问题描述】:

我正在尝试为uvaonlinejudge 上的 3n + 1 问题找到一个有效的解决方案。我的代码使用字典进行记忆。任何人都可以提出有助于缩短此代码执行时间的改进吗?目前,我在提交代码时收到“超出时间限制”错误。如果有人对此问题有有效的解决方案,请与我分享。请不要将此帖子标记为重复。我已经在 * 上看到了 this 和其他人的帖子,但他们没有回答此处发布的问题。我的代码如下:

import sys

def recCycleLength(n,cycLenDict):
    if n==1:
        return 1
    if n not in cycLenDict:
        if n%2==0:
            cycLen = recCycleLength(n//2, cycLenDict)
            cycLenDict[n] = cycLen + 1
            return cycLen+1
        else:
            cycLen = recCycleLength(3*n+1, cycLenDict)
            cycLenDict[n] = cycLen + 1
            return cycLen+1
    else:
        return cycLenDict[n]


def maxCycle(a, b):
    i = a
    mydict = {} 
    maxLength = 1
    while i <= b:
        m = recCycleLength(i, mydict)
        if m > maxLength:
            maxLength = m
        i = i + 1
    return maxLength


for line in sys.stdin:
    curr_line=line.split()
    num1 = int(curr_line[0])
    num2 = int(curr_line[1])
    if num1>num2:
        num1, num2 = num2, num1
    m = maxCycle(num1, num2)
    print("{} {} {}".format(num1, num2, m))

【问题讨论】:

  • 如果您有可行的解决方案,请改用codereview.stackexchange.com
  • 可以讨论:OP不想提高可读性,而是提高速度。
  • 如果您提交,请在问题中提供输入。我尝试在页面中输入示例,速度非常快。所以我错过了一些东西。
  • uvaonlinejudge 不向用户提供运行提交代码的测试用例。示例输入只是为了让用户可以在提交之前测试他们的代码是否有错误。

标签: python optimization


【解决方案1】:

我在您的代码中发现了问题。实际上,您并没有将上一个间隔生成的cycLenDict 保存为下一个。这就是为什么您的代码如此“缓慢”的原因,因为它会一遍又一遍地生成所有可能的结尾。只需在全局范围内移动它或进行如下操作:

import sys


def rec(n, cache):
    if n in cache:
        return cache[n]
    if n % 2 == 0:
        cycle = rec(n//2, cache)
    else:
        cycle = rec(3*n+1, cache)
    cache[n] = cycle + 1
    return cache[n]


def cycle(a, b, cache):
    return max(rec(i, cache) for i in range(a, b+1))


if __name__ == '__main__':
    cache = {1: 1}
    for line in sys.stdin:
        a, b = map(int, line.split())
        a, b = min(a, b), max(a, b)
        m = cycle(a, b, cache)
        print("{} {} {}".format(a, b, m))

【讨论】:

    【解决方案2】:

    通过将所有计算结果缓存在mydict 中,代码似乎做了正确的事情来优化执行maxCycle

    但是,应用程序的输入包含许多要处理的值对,maxCycle 将重置 mydict = {} 并从头开始计算所有内容。

    我建议改为在全局范围内记住结果。对原始代码的简单修改是:

    cycLenDict = {}   # global dictionary
    
    def recCycleLength(n):   # no cycLenDict argument
        if n==1:
            return 1
        if n not in cycLenDict:
            # ...
    
    def maxCycle(a, b):
        # ...
    
        while i <= b:
            m = recCycleLength(i)   # no myDict argument
    

    为了让一切看起来更好看(与上面的解决方案相比,性能方面没有任何差异),请创建一个能够记住结果的装饰器,这样其余的代码就不必处理这些了:

    def memoize(func):
        """decorate any function which takes positional arguments to cache its results"""
    
        func.results = {}  # results are stored globally as the funtion's attribute
    
        def memoized(*a):        # memoized version of func
            if a not in func.results:       # if not cached
                func.results[a] = func(*a)  # save to cache
            return func.results[a]          # return from cache
        return memoized
    
    
    @memoize       # with this, recCycleLength is called only once for every n value
    def recCycleLength(n):
        if n==1:
            return 1
        elif n%2==0:
            return recCycleLength(n//2) + 1
        else:
            return recCycleLength(3*n+1) + 1
    
    
    def maxCycle(a, b):
        return max(recCycleLength(i) for i in range(a, b+1))
    

    【讨论】:

      【解决方案3】:
      import sys
      def cycle(current, count=1):
      if current == 1:
          return count
      if current % 2 == 0:
          return cycle(current / 2, count + 1)
      return cycle(current * 3 + 1, count + 1)
      
      def max_cycles(lower, upper):
          max = 0
          for current in range(lower, upper):
              result = cycle(current, 1)
              if result > max:
                  max = result
          return max
      
      
      def main(i, j):
          if i > j:
              (i, j) = (j, i)
          print (i, j, max_cycles(i, j))
      
      
      if __name__ == "__main__":
          if len(sys.argv) < 3:   
              print 'usage: python filename argv1 argv2'
              print 'exiting'
              exit(0)
          main(int(sys.argv[1]), int(sys.argv[2]))
      

      【讨论】: