【问题标题】:how to generate Narcissistic numbers faster?如何更快地生成自恋数字?
【发布时间】:2013-01-03 14:36:04
【问题描述】:

“自恋数字”是 n 位数字,其中所有数字的 n 次方之和等于该数字。

所以,153 是一个自恋数字,因为1^3 + 5^3 + 3^3 = 153

现在给定 N,找出所有 N 位长度的自恋数字?

我的方法:是遍历所有数字做数字幂的总和

并检查它是否相同的数字,我计算了幂。

但这还不够好,有没有更快的方法?!

更新: 自然界中只有 88 个自恋数字,最大的是 39 位, 但我只需要长度为 12 或更少的数字。

我的代码:

long long int powers[11][12];
// powers[x][y] is x^y. and its already calculated

bool isNarcissistic(long long int x,int n){
    long long int r = x;
    long long int sum = 0;

    for(int i=0; i<n ; ++i){
        sum += powers[x%10][n];
        if(sum > r)
            return false;
        x /= 10;
    }
    return (sum == r);
}

void find(int n,vector<long long int> &vv){
    long long int start = powers[10][n-1];
    long long int end = powers[10][n];

    for(long long int i=start ; i<end ; ++i){
        if(isNarcissistic(i,n))
            vv.push_back(i);
    }
}

【问题讨论】:

  • N 有什么限制?这是一条非常重要的信息。
  • 将其强制到constexpr 函数中可能是一个更好的练习...
  • 12位计算需要多长时间?
  • @specialscope:使用像上面这样简单的算法,复杂性太差了,你可能永远不会知道结果。
  • 是的,我明白了,即使您对每个数字进行 1 次迭代,您也必须进行 999,999,999,999 次比较。所以我觉得有效地解决这个问题需要像 hadoop 这样的东西。

标签: c++ algorithm


【解决方案1】:

由于总共只有 88 个自恋数字,您可以将它们存储在查找表中并对其进行迭代:http://mathworld.wolfram.com/NarcissisticNumber.html

【讨论】:

  • 我认为练习的重点是自己去接触那些自恋的数字。
  • @specialscope 未在问题中指定,因此不是家庭作业解决机制
  • @Rami:你没这么说。这绝对是解决问题最有效的方法;)
  • 这太可悲了......所以在以 10 为底的自恋数字中寻找乐趣已经没有了。其他的底呢?数学世界页面仅显示“Pickover (1995) 给出了各种基数中最大的已知自恋数表”,因此可能还有其他基数?
  • @Krystian:自恋数字的数量对于任何基数都是有限的。
【解决方案2】:

从另一端开始。遍历d 数字的所有非递减序列的集合,计算d-th 次方的总和,并检查它是否产生(排序后)您开始使用的序列。

既然有

9×10^(d-1)

d-digit 数字,但仅限

(10+d-1) `choose` d

d 数字的非递减序列,将搜索空间减少了接近 d! 的因子。

【讨论】:

    【解决方案3】:

    下面的代码实现了@Daniel Fischer 的想法。它复制table referenced at Mathworld,然后再打印几个11位数字,并验证没有here所述的12位数字。

    生成所有可能的非递增数字字符串的直方图,而不是字符串本身,实际上会更简单并且可能更快一点。直方图是指索引为 0-9 的相应数字频率的表。这些可以直接比较,无需排序。但是下面的代码运行时间

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAX_DIGITS 12
    
    // pwr[d][n] is d^n
    long long pwr[10][MAX_DIGITS + 1];
    
    // Digits and final index of number being considered.
    int digits[MAX_DIGITS];
    int m;
    
    // Fill pwr.
    void fill_tbls(void)
    {
      for (int d = 0; d < 10; d++) {
        pwr[d][0] = 1;
        for (int p = 1; p <= MAX_DIGITS; p++) 
          pwr[d][p] = pwr[d][p-1] * d;
      }
    }
    
    // qsort comparison for integers descending
    int cmp_ints_desc(const void *vpa, const void *vpb)
    {
      const int *pa = vpa, *pb = vpb;
      return *pb - *pa;
    }
    
    // Test current number and print if narcissistic.
    void test(void)
    {
      long long sum = 0;
      for (int i = 0; i <= m; i++)
        sum += pwr[digits[i]][m + 1];
      int sum_digits[MAX_DIGITS * 2];
      int n = 0;
      for (long long s = sum; s; s /= 10)
        sum_digits[n++] = s % 10;
      if (n == m + 1) {
        qsort(sum_digits, n, sizeof(int), cmp_ints_desc);
        if (memcmp(sum_digits, digits, n * sizeof(int)) == 0) 
          printf("%lld\n", sum);
      }
    }
    
    // Recursive generator of non-increasing digit strings.
    // Calls test when a string is complete.
    void gen(int i, int min, int max)
    {
      if (i > m) 
        test();
      else {
        for (int d = min; d <= max; d++) {
          digits[i] = d;
          gen(i + 1, 0, d); 
        }
      }
    }
    
    // Fill tables and generate.
    int main(void)
    {
      fill_tbls();
      for (m = 0; m < MAX_DIGITS; m++)
        gen(0, 1, 9);
      return 0;
    }
    

    【讨论】:

      【解决方案4】:

      我在 Lua 中编写了一个程序,它在 30829.642 秒内找到了所有自恋数字。该程序的基础是递归数字值计数数组生成器函数,它在为所有数字值生成数字值计数时调用检查函数。每个嵌套循环迭代:

      从我= 对于 x,0 和 a+x*d^o+(s-x)*(d-1)^o >= 10^(o-1) 的解中的较大者 在哪里 - 'a' 是迄今为止数字的幂的累积和, - 'd' 是当前的数字值(0-9 为基数 10), - 'o' 是总位数(数字值计数数组的总和必须相加), - 's' 表示在数组添加到 'o' 之前剩余的可用插槽

      最多 i

      这可确保检查的数字始终具有与“o”相同的位数,因此更容易自恋,同时避免不必要的计算。

      在循环中,它执行递归调用,递减数字值 'd' 添加当前数字值的贡献 (a=a+i*d^o) 并占用已用完的 i 个数字槽远离's'。

      我写的要点是:

      local function search(o,d,s,a,...) --Original number of digits, digit-value, remaining digits, accumulative sum, number of each digit-value in descending order (such as 5 nines)
          if d>0 then
              local d0,d1=d^o,(d-1)^o
              local dd=d0-d1
              --a+x*d^o+(s-x)*(d-1)^o >= 10^(o-1) , a+x*d^o < 10^o
              for i=max(0,floor((10^(o-1)-s*d1-a)/dd)),min(s,ceil((10^o-a)/dd)-1) do
                  search(o,d-1,s-i,a+i*d0,i,...) --The digit counts are passed down as extra arguments.
              end
          else
              --Check, with the count of zeroes set to 's', if the sum 'a' has the same count of each digit-value as the list specifies, and if so, add it to a list of narcissists.
          end
      end
      
      local digits=1 --Skip the trivial single digit narcissistic numbers.
      while #found<89 do
          digits=digits+1
          search(digits,9,digits,0)
      end
      

      编辑:我忘了提到我的程序找到了 89 个自恋数字!以下是它的发现:

      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474, 54748, 92727, 93084, 548834, 1741725, 4210818, 9800817, 9926315, 24678050, 24678051, 88593477, 146511208, 472335975, 534494836, 912985153, 4679307774, 32164049650, 32164049651, 40028394225, 42678290603, 44708635679, 49388550606, 82693916578, 94204591914, 28116440335967, 4338281769391370, 4338281769391371, 21897142587612075, 35641594208964132, 35875699062250035, 1517841543307505039, 3289582984443187032, 4498128791164624869, 4929273885928088826, 63105425988599693916, 128468643043731391252,449177399146038697307, 21887696841122916288858, 27879694893054074471405, 27907865009977052567814, 28361281321319229463398, 35452590104031691935943, 174088005938065293023722, 188451485447897896036875, 239313664430041569350093, 1550475334214501539088894, 1553242162893771850669378, 3706907995955475988644380, 3706907995955475988644381, 4422095118095899619457938, 121204998563613372405438066, 121270696006801314328439376, 128851796696487777842012787, 174650464499531377631639254, 177265453171792792366489765, 14607640612971980372614873089, 19008174136254279995012734740, 19008174136254279995012734741, 23866716435523975980390369295, 1145037275765491025924292050346, 1927890457142960697580636236639, 2309092682616190307509695338915, 17333509997782249308725103962772, 186709961001538790100634132976990, 186709961001538790100634132976991, 1122763285329372541592822900204593, 12639369517103790328947807201478392, 12679937780272278566303885594196922, 1219167219625434121569735803609966019, 12815792078366059955099770545296129367, 115132219018763992565095597973971522400, 115132219018763992565095597973971522401
      

      【讨论】:

        【解决方案5】:

        对于后代 ;-) 这与@Krakow10 的方法最相似,递归地生成数字包,从 9 开始,然后是 8,然后是 7 ... 到 0。

        它是 Python3 代码,可在不到 10 分钟的时间内(在我的盒子上)找到所有 1 到 61 位(第一个“显然不可能”宽度)的 base-10 解决方案。这是迄今为止我听说过的最快的解决这个问题的代码。有什么诀窍?没有技巧 - 只是乏味;-) 随着我们的进行,到目前为止,部分总和产生了对可行延续的限制。代码只关注那些,因此能够尽早切断大量搜索。

        注意:这找不到 0。我不在乎。虽然所有参考文献都说有 88 个解决方案,但他们的表都有 89 个条目。一定是某个热心的编辑器后来加了“0”,然后其他人都盲目地复制了它;-)

        编辑新版本的速度是原来的两倍多,通过在搜索早期利用一些部分和约束 - 现在在我的盒子上只需 4 分钟多一点。

        def nar(width):
            from decimal import Decimal as D
            import decimal
            decimal.getcontext().prec = width + 10
            if width * 9**width < 10**(width - 1):
                raise ValueError("impossible at %d" % width)
            pows = [D(i) ** width for i in range(10)]
            mintotal, maxtotal = D(10)**(width - 1), D(10)**width - 1
        
            def extend(d, todo, total):
                # assert d > 0
                powd = pows[d]
                d1 = d-1
                powd1 = pows[d1]
                L = total + powd1 * todo # largest possible taking no d's
                dL = powd - powd1  # the change to L when i goes up 1
                for i in range(todo + 1):
                    if i:
                        total += powd
                        todo -= 1
                        L += dL
                        digitcount[d] += 1
                    if total > maxtotal:
                        break
                    if L < mintotal:
                        continue
                    if total < mintotal or L > maxtotal:
                        yield from extend(d1, todo, total)
                        continue
                    # assert mintotal <= total <= L <= maxtotal
                    t1 = total.as_tuple().digits
                    t2 = L.as_tuple().digits
                    # assert len(t1) == len(t2) == width
                    # Every possible continuation has sum between total and
                    # L, and has a full-width sum.  So if total and L have
                    # some identical leading digits, a solution must include
                    # all such leading digits.  Count them.
                    c = [0] * 10
                    for a, b in zip(t1, t2):
                        if a == b:
                            c[a] += 1
                        else:
                            break
                    else:  # the tuples are identical
                        # assert d == 1 or todo == 0
                        # assert total == L
                        # This is the only sum we can get - no point to
                        # recursing.  It's a solution iff each digit has been
                        # picked exactly as many times as it appears in the
                        # sum.
                        # If todo is 0, we've picked all the digits.
                        # Else todo > 0, and d must be 1:  all remaining
                        # digits must be 0.
                        digitcount[0] += todo
                        # assert sum(c) == sum(digitcount) == width
                        if digitcount == c:
                            yield total
                        digitcount[0] -= todo
                        continue
                    # The tuples aren't identical, but may have leading digits
                    # in common.  If, e.g., "9892" is a common prefix, then to
                    # get a solution we must pick at least one 8, at least two
                    # 9s, and at least one 2.
                    if any(digitcount[j] < c[j] for j in range(d, 10)):
                        # we're done picking digits >= d, but don't have
                        # enough of them
                        continue
                    # for digits < d, force as many as we need for the prefix
                    newtodo, newtotal = todo, total
                    added = []
                    for j in range(d):
                        need = c[j] - digitcount[j]
                        # assert need >= 0
                        if need:
                            newtodo -= need
                            added.append((j, need))
                    if newtodo < 0:
                        continue
                    for j, need in added:
                        newtotal += pows[j] * need
                        digitcount[j] += need
                    yield from extend(d1, newtodo, newtotal)
                    for j, need in added:
                        digitcount[j] -= need
                digitcount[d] -= i
        
            digitcount = [0] * 10
            yield from extend(9, width, D(0))
            assert all(i == 0 for i in digitcount)
        
        if __name__ == "__main__":
            from datetime import datetime
            start_t = datetime.now()
            width = total = 0
            while True:
                this_t = datetime.now()
                width += 1
                print("\nwidth", width)
                for t in nar(width):
                    print("   ", t)
                    total += 1
                end_t = datetime.now()
                print(end_t - this_t, end_t - start_t, total)
        

        【讨论】:

          【解决方案6】:

          我认为这个想法是生成相似的数字。例如,61 与 16 相似,因为您只是求和

          6^n +1^n

          所以

          6^n+1^n=1^n+6^n

          通过这种方式,您可以减少大量数字。例如在 3 位数场景中,

          121==112==211,

          你明白了。您需要先生成这些数字。 而且您需要生成这些数字,而无需实际从 0-n 迭代。

          【讨论】:

          • 我认为这不会有帮助。 153 是自恋的,但这意味着 135、513、531、315 和 351 不能自恋。
          【解决方案7】:

          Python 版本为:

          def generate_power_list(power):
          return [i**power for i in range(0,10)]
          
          
          def find_narcissistic_numbers_naive(min_length, max_length):
          for number_length in range(min_length, max_length):
          
              power_dict = generate_power_dictionary(number_length)
          
              max_number = 10 ** number_length
              number = 10** (number_length -1)
              while number < max_number:
          
                  value = 0
                  for digit in str(number):
                      value += power_dict[digit]
          
                  if value == number:
                      logging.debug('narcissistic %s ' % number)
          
                  number += 1
          

          递归解决方案:

          在此解决方案中,每个递归处理正在使用的数字数组中的单个数字,并尝试该数字的所有适当组合

          def execute_recursive(digits, number_length):
          index = len(digits)
          if digits:
              number = digits[-1]
          else:
              number = 0
          results = []
          digits.append(number)    
          
          if len(digits) < number_length:
              while number < 10:
                  results += execute_recursive(digits[:], number_length)
                  number += 1
                  digits[index] = number
          
          else:
              while number < 10:
                  digit_value = sum_digits(digits)
                  if check_numbers_match_group(digit_value, digits):
                      results.append(digit_value)
                      logging.debug(digit_value)
          
                  number += 1
                  digits[index] = number
          
          return results
          
          
          def find_narcissistic_numbers(min_length, max_length):
          for number_length in range(min_length, max_length):
              digits = []
              t_start = time.clock()
              results = execute_recursive(digits, number_length)
              print 'duration: %s for number length: %s' %(time.clock() - t_start, number_length)
          

          自恋号码检查 在基础版本中,当检查数字是否与数字匹配时,我们会遍历每个数字类型,以确保每种类型的数字相同。在这个版本中,我们增加了在进行全面检查之前检查数字长度是否正确的优化。

          我预计这会对较小的数字长度产生更大的影响,因为随着数字长度的增加,分布中间的数字往往会更多。结果在一定程度上支持了这一点:

          1. n=16:提高 11.5%
          2. n=19:提高 9.8%
          def check_numbers_match_group(number, digits):
          number_search = str(number)
          
          # new in v1.3
          if len(number_search) != len(digits):
              return False
          
          for digit in digit_list:
              if number_search.count(digit[0]) != digits.count(digit[1]):
                  return False
          
          return True
          

          【讨论】:

            【解决方案8】:

            如果它是自恋数字,我认为您可以使用 Multinomial theorem 进行一些优化。
            您可以计算 (a+b+c+..)^n- 非 n 次幂值的总和
            例如对于 n=2 你应该比较 x 和 (a+b)^2-2*a*b 其中 a 和 b 是数字 x

            【讨论】:

            • 问题不在于幂,正如我所说的已经计算出幂,问题在于搜索特定范围内的所有数字的算法。
            【解决方案9】:
            '''We can use Nar() function to calculate the Narcissitic Number.'''
            
            import math
            def Nar(num):
               sum=0
               n=len(str(num))
               while n>0:
                 temp=num%10
                 sum=sum+math.pow(temp,n)
                 num=num/10
               return sum
            x=input()
            y=Nar(x)
            if x==y:
              print x,' is a Narcissistic number'
            else:
             print x,' is not a Narcissistic number'
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-09-23
              • 2011-03-21
              • 2019-11-19
              • 1970-01-01
              • 2020-06-07
              • 1970-01-01
              • 2020-06-15
              相关资源
              最近更新 更多