【问题标题】:Euler problem number #4欧拉问题编号 #4
【发布时间】:2009-02-16 23:21:46
【问题描述】:

使用 Python,我正在尝试解决 problem #4 中的 Project Euler 问题。有人可以告诉我我做错了什么吗?问题是找出由两个 3 位数乘积构成的最大回文数。这是我到目前为止所拥有的。

import math

def main(): 
    for z in range(100, 1000):
        for y in range(100, 1000):
            for x in range(1, 1000000):
                x = str(x)
                if x == x[::-1] and x == z*y:
                    print x 

if __name__ == '__main__':
    main()

【问题讨论】:

  • 什么不起作用?它会出错吗?请提供堆栈跟踪。它似乎永远运行吗?嗯...也许这就是它很难的原因。
  • 这不会打印任何东西,因为他正在比较一个字符串和一个 int。
  • @Baltimark:还是因为它试图迭代近一万亿个项目而不打印?如果您可以每秒评估 10,000 个,则只需大约 3 年即可完成。
  • marc,你能重新命名这个问题以表明它是针对#4的吗?
  • 对不起你完全正确它的实际问题 4

标签: python palindrome


【解决方案1】:

一些效率问题:

  1. 从顶部开始(因为我们可以使用它来跳过大量计算)
  2. 不要重复计算
def is_palindrome(n):
    s = str(n)
    return s == s[::-1]

def biggest():
    big_x, big_y, max_seen = 0,0, 0
    for x in xrange(999,99,-1):
        for y in xrange(x, 99,-1):  # so we don't double count   
            if x*y < max_seen: continue  # since we're decreasing, 
                                # nothing else in the row can be bigger
            if is_palindrome(x*y):
                big_x, big_y, max_seen = x,y, x*y

    return big_x,big_y,max_seen

biggest()
# (993, 913, 906609)

【讨论】:

  • 您可以将内部循环写为“for y in xrange(x, 99, -1)”,从而节省一些计算。
  • 这主要是一个错字。现在应该很紧了。
  • 这也意味着您可以删除“if y > x: continue”。但是,是的,很好的解决方案。 +1
  • 哇,这次看错了。实际上 "if (x*y) > max_seen:" 现在是多余的语句。
  • 已修复,感谢 nikhil!试图在飞行中做这些事情,一个人会犯错误。
【解决方案2】:

尝试从 z 和 y 的乘积计算 x,而不是检查从 1 到 100 万之间的每个数字。想一想:如果要求您计算 500*240,哪个更有效 - 将它们相乘,还是从 1 开始计数直到找到正确答案?

【讨论】:

    【解决方案3】:

    以下是一些需要牢记的一般优化。发布的代码处理所有这些,但这些是学习的一般规则,可能有助于解决未来的问题:

    1) 如果您已经检查过 z = 995, y = 990,则无需检查 z = 990, y = 995。Greg Lind 妥善处理了这个问题

    2) 你计算 z*y 的乘积,然后在一个很大的范围内运行 x 并将该值与 y*z 进行比较。例如,你刚刚计算了 900*950,然后你将 x 从 1000 运行到 1M,看看 x = 900*950。你看到这个问题了吗?

    3) 另外,下面的代码会发生什么? (这就是为什么您的代码没有返回任何内容,但无论如何您都不应该这样做)

    x = str(100)
    y = 100
    print x == y
    

    4) 如果您算出 (3),您将在那里打印大量信息。您需要找出一种存储最大值的方法,并且只在最后返回该值。

    5) 这是计算欧拉问题的好方法:

    if __name__ == "__main__":
        import time
        tStart = time.time()
        print "Answer = " + main()
        print "Run time = " + str(time.time() - tStart)
    

    【讨论】:

    • 哦,是的。 . . 还有一种方法可以在数学上处理回文,但我的方法似乎并不比转换为字符串和检查更快。不过,这是一个很好的练习。
    • +1 好答案。此外,还有一个用于准确计时的内置库,从 Python 2.5 开始:import cProfile; cProfile.run('biggest()', None, 'cumulative')
    【解决方案4】:

    而不是枚举所有 3 位数字的乘积(~900^2 次迭代), 枚举所有 6 位和 5 位回文数(这需要大约 1000 次迭代); 然后为每个回文决定它是否可以用一个产品来表示 两个 3 位数的数字(如果不能,它应该有一个 4 位数的素数, 所以这很容易测试)。

    另外,你问的是问题#4,而不是#3。

    【讨论】:

    • 在这种情况下,编写一个记忆因子分解器比只提取数字更乏味。
    • 而且因式分解也不是免费的,所以你不会获得那么多,如果有的话。
    【解决方案5】:

    比较字符串和整数

    x == z*y
    

    还有逻辑错误

    以相反的顺序开始range(999, 99, -1)。那会更有效率。完全删除第三个循环和第二个比较。

    【讨论】:

      【解决方案6】:

      哇,与此页面上的其他实现相比,这种方法改进了不少,包括mine

      而不是

      • 逐行遍历三位数因子(首先对 x = 999 执行所有 y,然后对 x = 998 执行所有 y,等等),

      我们

      • 沿着对角线走:首先做所有的 x, y 使得 x + y = 999 + 999;然后做所有的 x, y 使得 x + y = 999 + 998;等

      不难证明,在每条对角线上,x 和 y 越接近,乘积越高。所以我们可以从中间开始,x = y(或 x = y + 1 用于奇数对角线),仍然像以前一样进行短路优化。而且因为我们可以从最高的对角线开始,也就是最短的对角线,所以我们可能会更快地找到最高的合格回文。

      maxFactor = 999
      minFactor = 100
      
      def biggest():
          big_x, big_y, max_seen, prod = 0, 0, 0, 0
      
          for r in xrange(maxFactor, minFactor-1, -1):
              if r * r < max_seen: break
      
              # Iterate along diagonals ("ribs"):
      
              # Do rib x + y = r + r
              for i in xrange(0, maxFactor - r + 1):
                  prod = (r + i) * (r - i)
      
                  if prod < max_seen: break
      
                  if is_palindrome(prod):
                      big_x, big_y, max_seen = r+i, r-i, prod
      
              # Do rib x + y = r + r - 1
              for i in xrange(0, maxFactor - r + 1):
                  prod = (r + i) * (r - i - 1)
      
                  if prod < max_seen: break
      
                  if is_palindrome(prod):
                      big_x, big_y, max_seen = r+i,r-i-1, prod
      
          return big_x, big_y, max_seen
      
      # biggest()
      # (993, 913, 906609)
      

      几乎是 3 倍的改进

      我们现在只调用了 2228 次,而不是调用 is_palindrome() 6124 次。并且总累积时间已经从大约 23 毫秒下降到大约 9 毫秒!

      我仍然想知道是否存在完全线性 (O(n)) 的方式来生成按降序排列的两组数字的乘积列表。但我对上面的算法很满意。

      【讨论】:

        【解决方案7】:

        问题说明:

        What is the largest prime factor of the number 600851475143?
        

        我使用 C# 解决了这个问题,但算法本身与语言无关。

        1. 创建一种方法来确定一个数是否为质数。这可能是蛮力的(而不是使用更有效的筛选算法),看起来像这样:

        private static long IsPrime(long input)
                {
                    if ((input % 2) == 0)
                    {
                        return 2;
                    }
                    else if ((input == 1))
                    {
                        return 1;
                    }
                    else
                    {
                        long threshold = (Convert.ToInt64(Math.Sqrt(input)));
                        long tryDivide = 3;
                        while (tryDivide < threshold)
                        {
                            if ((input % tryDivide) == 0)
                            {
                                Console.WriteLine("Found a factor: " + tryDivide);
                                return tryDivide;
                            }
                            tryDivide += 2;
                        }
                        Console.WriteLine("Found a factor: " + input);
                        return -1;
                    }
                }
        
        1. 一旦我有了一个确定素数的函数,我就可以使用这个函数来找到最高的素因子

        private static long HighestPrimeFactor(long input)
        {
            bool searching = true;
            long highestFactor = 0;
            while (searching)
            {
                long factor = IsPrime(input);
                if (factor != -1)
                {
                    theFactors.Add(factor);
                    input = input / factor; 
                }
                if (factor == -1)
                {
                    theFactors.Add(input);
                    highestFactor = theFactors.Max();
                    searching = false;
                }
            }
            return highestFactor;
        }
        

        我希望这会有所帮助,但不会放弃太多。

        【讨论】:

        • 我相信 OP 意味着问题 4
        • 该算法可能是不可知的,但如果您不了解该语言,则可能难以遵循 C# 实现。伪代码会更好。
        • 原发帖人打错字,当他的意思是 4 时说 3;后来编辑。
        【解决方案8】:

        这里的其他建议很棒。此代码也有效。我从 999 开始,因为我们知道可能的最大组合是 999*999。不是python,而是一些快速完成的伪代码。

        public static int problem4()
            {       
            int biggestSoFar=0;
                for(int i = 999; i>99;i--){
                    for(int j=999; j>99;j--){
                        if(isPaladrome(i*j))
                           if(i*j>biggestSoFar)
                                biggestSoFar=i*j;
                    }
                }
                return biggestSoFar;    
            }
        

        【讨论】:

          【解决方案9】:

          这为@GreggLind 的优秀解决方案添加了一些优化,将运行时间缩短了一半:

          def is_palindrome(n):
              s = str(n)
              return s == s[::-1]
          
          def biggest():
              big_x, big_y, max_seen = 0,0, 0
              for x in xrange(999,99,-1):
                  # Optim. 1: Nothing in any row from here on can be bigger.
                  if x*x < max_seen: break  
          
                  for y in xrange(x, 99,-1):  # so we don't double count   
                      # Optim. 2: break, not continue
                      if x*y < max_seen: break  # since we're decreasing, 
                                          # nothing else in the row can be bigger
          
                      if is_palindrome(x*y):
                          big_x, big_y, max_seen = x,y, x*y
          
              return big_x,big_y,max_seen
          
          biggest()
          # (993, 913, 906609)
          

          线

          if x*x < max_seen: break
          

          意味着一旦我们到达 x 小于迄今为止最大回文的 sqrt 的点,我们不仅不需要研究该行上的任何其他因素;我们甚至根本不需要调查更多的行,因为所有剩余的行都将从小于当前 x 值的数字开始。

          这并没有减少我们调用is_palindrome() 的次数,但它意味着外循环的迭代次数减少了很多。它中断的x 的值是952,因此我们已经消除了对853 行的检查(尽管是“较小”的行,这要感谢另一个break)。

          我也注意到了

          if x*y < max_seen: continue
          

          应该是

          if x*y < max_seen: break
          

          我们正在尝试使整行短路,而不仅仅是内部循环的当前迭代。

          当我使用 cProfile 运行此脚本时,在优化之前,biggest() 的累积时间平均约为 56 毫秒。优化将其缩小到大约 23 毫秒。单独的任何一种优化都可以带来大部分改进,但第一个比第二个更有用。

          【讨论】:

          • 我还想看看我们不是在行上而是在对角线上运行是否有任何收获,其中在每条对角线上,x + y 是恒定的,从 999+999 倒数。
          【解决方案10】:

          这是您可以考虑的解决方案。它可能会更有效率,但只需要一点时间即可运行。

          largest = 0
          for a in range(100, 1000):
              for b in range(100, 1000):
                  c = a * b
                  if str(c) == ''.join(reversed(str(c))):
                      largest = max(largest, c)
          print(largest)
          

          【讨论】:

            【解决方案11】:

            这是一个高效的通用解决方案(比我见过的其他解决方案快约 5 倍):

            def pgen(factor):
                ''' Generates stream of palindromes smaller than factor**2 
                    starting with largest possible palindrome '''
                pmax = str(factor**2)
                half_palindrome = int(pmax[0:len(pmax)/2]) - 1
                for x in xrange(half_palindrome, 0, -1):
                    yield int(str(x) + str(x)[::-1])
            
            def biggest(factor):
                ''' Returns largest palindrome and factors '''
                for palindrome in pgen(factor):
                    for f1 in xrange(factor/11*11, factor/10, -11):
                        f2 = palindrome/f1
                        if f2 > factor:
                            break
                        if f2*f1 == palindrome:
                            return palindrome, f1, f2
            
            >>> biggest(99)
            (9009, 99, 91)
            >>> biggest(999)
            (906609, 993, 913)
            >>> biggest(9999)
            (99000099, 9999, 9901)
            >>> biggest(99999)
            (9966006699L, 99979, 99681L)
            >>> biggest(9999999)
            (99956644665999L, 9998017, 9997647L)
            >>> biggest(99999999)
            (9999000000009999L, 99999999, 99990001L)
            >>> biggest(999999999)
            (999900665566009999L, 999920317, 999980347L)
            

            【讨论】:

              【解决方案12】:

              这段代码效率更高。我还包括了一个打印语句来输出完成所用的时间。

              import time
              start_time = time.time()
              
              def palindrome(n):
                  if str(n)==str(n)[-1::-1]:
                      return True
                  else:
                      return False
              def highestprod(n):
                  global x
                  x=''
                  for i in range (n):
                      x+='9'
                  x=int(x)
                  return x**2
              
              def smallestprod(n):
                  global y
                  y=''
                  for i in range (n):
                      y+='1'
                  y=int(y)
                  return y**2
              
              def lpp(n):  #where n is the no of digits 
                  lst=[]
                  k=highestprod(n)
                  l=smallestprod(n)
                  for i in range(k,int(k/1.15),-1):
                      if i%10==0:
                          pass
                      elif palindrome(i):
                          lst.append(i)
                  val=0
                  for m in lst:
                      no=int(x)
                      while True:
                          if no<y:
                              break
                          elif m%no==0 and x>(m/no)>y and no>y:
                              val=m
                              break
                          else:
                              no-=1
                      if val:
                          break
                  print(val)
              
              lpp(3) #<<<==== type here. Can find the answer for n=2,3 and 4 (4 might take 9-10 seconds) but n>4 takes so much time.
              
              print("--- %s seconds ---" % (time.time() - start_time))
              

              我试过 lpp(5)。花了太多时间,所以我终止了程序。

              【讨论】:

                【解决方案13】:

                如果您的程序运行缓慢,并且您有这样的嵌套循环:

                for z in range(100, 1000):
                    for y in range(100, 1000):
                        for x in range(1, 1000000):
                

                那么您应该问自己一个问题:“最内层循环的主体将执行多少次?” (最内层循环的主体是以:x = str(x) 开头的代码)

                在这种情况下,很容易弄清楚。外部循环将执行 900 次。 对于每次迭代,中间循环也将执行 900 次,即 900×900 或 810,000 次。然后,对于这 810,000 次迭代中的每一次,内部循环本身将执行 999,999 次。我想我需要很长的时间来计算:

                >>> 900*900*999999
                809999190000L
                

                换句话说,您的回文检查几乎是 8100 亿次 次。如果您想将其纳入 Project Euler 建议的每个问题 1 分钟的限制,您可能需要稍微优化一下 :-)(请参阅 David 的评论)

                【讨论】:

                  【解决方案14】:

                  这就是我在 Java 中所做的:

                  public class Euler0004
                  {
                      //assumes positive int
                      static boolean palindrome(int p)
                      {
                          //if there's only one char, then it's
                          //  automagically a palindrome
                          if(p < 10)
                              return true;
                  
                          char[] c = String.valueOf(p).toCharArray();
                  
                          //loop over the char array to check that
                          //  the chars are an in a palindromic manner
                          for(int i = 0; i < c.length / 2; i++)
                              if(c[i] != c[c.length-1 - i])
                                  return false;
                  
                          return true;
                      }
                  
                  
                      public static void main(String args[]) throws Exception
                      {
                          int num;
                          int max = 0;
                  
                          //testing all multiples of two 3 digit numbers.
                          // we want the biggest palindrome, so we
                          // iterate backwards
                          for(int i = 999; i > 99; i--)
                          {
                              // start at j == i, so that we
                              //  don't calc 999 * 998 as well as
                              //  998 * 999...
                              for(int j = i; j > 99; j--)
                              {
                                  num = i*j;
                  
                                  //if the number we calculate is smaller
                                  //  than the current max, then it can't
                                  //  be a solution, so we start again
                                  if(num < max)
                                      break;
                  
                                  //if the number is a palindrome, and it's
                                  //  bigger than our previous max, it
                                  //  could be the answer
                                  if(palindrome(num) && num > max)
                                      max = num;
                              }
                          }
                  
                          //once we've gone over all of the numbers
                          //  the number remaining is our answer
                          System.out.println(max);
                  
                      }
                  }
                  

                  【讨论】:

                    【解决方案15】:

                    这是我的解决方案:

                    polindroms = [(x, y, x * y) for x in range(100, 999) for y in range(100, 999) if str(x * y) == str(x * y)[::-1]]
                    print max(polindroms, key = lambda item : item[2])
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-05-20
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多