找到8位数字的总和的有效方法

时间:2013-12-09 18:00:51

标签: c

我必须找到前4位数的总和,最后4位数的总和并比较它们(m和n之间的所有数字)。但是,当我在线提交我的解决方案时,时间限制存在问题。

这是我的代码:

#include <stdio.h>

int main()
{
    int M, N, res = 0, cnt, first4, second4, sum1, sum2;

    scanf("%d", &M);
    scanf("%d", &N);

    for(cnt = M; cnt <= N; cnt++)
    {
        first4 = cnt % 10000;
        sum1 = first4 % 10 + (first4 / 10) % 10 + (first4 / 100) % 10 + (first4 / 1000) % 10;
        second4 = cnt / 10000;
        sum2 = second4 % 10 + (second4 / 10) % 10 + (second4 / 100) % 10 + (second4 / 1000) % 10;

        if(sum1 == sum2)
            res++;

    }

    printf("%d", res);


    return 0;
}

我正在努力寻找一种更有效的方法来实现这一目标。

7 个答案:

答案 0 :(得分:5)

我不知道这是否会明显更快,但您可以尝试将数字分成两个4位数字,然后使用表格查找来获得总和。这样,只有一个除法运算而不是八个。

你可以预先计算10000个总和的表,这样就可以编译它,所以根本就没有运行时成本。

可以使用的另一个稍微复杂但可能更快的方法是使用10000个元素的表或地图,它与总和查找表相反,您可以将总和映射到四位数字的集合。产生这笔钱。这样,当您必须找到特定范围10000数字范围的结果时,它是对最重要的四位数之和的简单查找。例如,要查找范围12340000 - 12349999的结果,您可以在反向查找表上使用二进制搜索来快速查找0 - 9999范围内有多少数字10(1 + 2 + 3 + 4)

再次 - 这个反向求和查找表可以预先计算并作为静态数组编译。

通过这种方式,完成10000个数字范围的结果可以通过几个二进制搜索来执行。由于必须忽略来自感兴趣范围之外的匹配,因此也可以使用反向查找表来处理任何部分范围,并且稍微复杂一些。但是,对于整个子范围集,这种并发症最多只能发生两次。

这会将算法的复杂度从O(N * N)降低到O(N log N)(我认为)。


更新

这是我得到的一些时间(Win32-x86,使用VS 2013(MSVC 12)和发布版本默认选项):

       range    range     
       start    end        count    time
================================================

alg1(10000000, 99999999): 4379055, 1.854 seconds
alg2(10000000, 99999999): 4379055, 0.049 seconds
alg3(10000000, 99999999): 4379055, 0.001 seconds

使用:

  • alg1()是问题的原始代码
  • alg2()是我的第一个剪切建议(查找预先计算的总和)
  • alg3()是第二个建议(使用按总和排序的表格对和词匹配进行二元搜索查找)

我对alg1()alg2()

之间的区别感到惊讶

答案 1 :(得分:5)

最后,如果你仍然感兴趣,有一个更快的方法来做到这一点。 您的任务并不特别要求您计算所有数字的总和, 它只询问一些特殊号码的数量。 在这种情况下,记忆或动态编程等优化技术非常方便。

在这种情况下,如果你有一些数字的前四位数(让它们是1234), 你计算他们的总和(在这种情况下是10),你马上知道, 应该是其他四位数的总和是多少。

产生和10的任何4位数字现在可以是创建有效数字的另一半。 因此,以1234开头的有效数字的总数正好是给出总和10的所有四位数字的数字。

现在考虑另一个数字,比如3412.这个数字也等于10, 因此,任何完成1234的权利也完成3412。

这意味着以3412开头的有效数字是相同的 作为以1234开头的有效数字的数量,其中与有效数字的总数相同,其中前半部分产生总和10

因此,如果我们为每个i预先计算四位数的数量 得到总和i,我们知道每个前四位数的确切数 完成有效数字的最后四位数的组合, ,无需迭代所有10000个

此算法的以下实现

  1. 预计算开始一半的每个总和的不同结束一半的数量
  2. 在三个子区间内分割[M,N]区间,因为在第一个和最后一个开始不是每个结束都是可能的
  3. 此算法以比原始实现方式更快的方式运行(对于足够大的N-M)。

    #include <string.h>
    
    int sum_digits(int number) {
        return number%10 + (number/10)%10 + (number/100)%10 + (number/1000)%10;
    }
    
    int count(int M, int N) {
        if (M > N) return 0;
    
        int ret = 0;
        int tmp = 0;
    
        // for each i from 0 to 36 precompute number of ways we can get this sum
        // out of a four-digit number
        int A[37];
        memset(A, 0, 37*4);
        for (int i = 0; i <= 9999; ++i) {
            ++A[sum_digits(i)];
        }
    
        // nearest multiple of 10000 greater than M
        int near_M = ((M+9999)/10000)*10000;
    
        // nearest multiple of 10000 less than N
        int near_N = (N/10000)*10000;
    
        // count all numbers up to first multiple of 10000
        tmp = sum_digits(M/10000);
        if (near_M <= N) {
            for (int i = M; i < near_M; ++i) {
                if (tmp == sum_digits(i % 10000)) {
                    ++ret;
                }
            }
        }
    
        // count all numbers between the 10000 multiples, use the precomputed values
        for (int i = near_M / 10000; i < near_N / 10000; ++i) {
            ret += A[sum_digits(i)];
        }
    
        // count all numbers after the last multiple of 10000
        tmp = sum_digits(N / 10000);
        if (near_N >= M) {
            for (int i = near_N; i <= N; ++i) {
                if (tmp == sum_digits(i % 10000)) {
                    ++ret;
                }
            }
        }
    
        // special case when there are no multiples of 10000 between M and N
        if (near_M > near_N) {
            for (int i = M; i <= N; ++i) {
                if (sum_digits(i / 10000) == sum_digits(i % 10000)) {
                    ++ret;
                }
            }
        }
    
        return ret;
    }
    
    编辑:我修复了评论中提到的错误。

答案 2 :(得分:3)

你这是错误的方式。一点点聪明都值得大量的马力。你不应该比较每个数字的第一个和最后四个数字。

首先 - 注意前四位数字的变化非常缓慢 - 所以你肯定可以在最后四位数字中有一个10000的循环,而无需重新计算第一笔数。

秒 - 数字的总和每第9个数字重复一次(直到你溢出)。这是“如果数字的总和可被9整除,则数字可被9整除”的基础。例如:

1234  - sum = 10
1234 + 9 = 1243   - sum is still 10

这意味着以下内容可以很好地工作(伪代码):

take first 4 digits of M, find sum (call it A)
find sum of last four digits of M (call it B)
subtract: C = (A - B)
If C < 9:
  D = C%9
  first valid number is [A][B+D]. Then step by 9, until...

你需要考虑一下“直到”,以及当C&gt; = 9时要做什么。这意味着你需要在B中找到零并用9替换它,然后重复上述步骤。

如果您不想做任何其他事情,那么请注意您不需要重新计算未更改的数字总和。通常,当你向数字加1时,数字之和增加1(除非有进位 - 然后它减少9;并且每9日发生一次,第99次(两次 - >总和下降18次),第999次(下降)由27)等。

我希望这可以帮助您以不同的方式思考问题。

答案 3 :(得分:2)

我将尝试一种不使用查找表的方法(即使我知道第二个应该更快),以研究我们可以加速多少只是优化微积分。该算法可用于堆栈是重要资源的地方...... 让我们研究分区和模数很慢的想法,例如在cortex R4中,32位除法需要多达16个循环,而乘法可以在单个循环中完成,而旧的ARM可能会更糟。 这个基本想法将尝试使用数字数组而不是整数来摆脱它们。为了简单起见,让我们在pseudo optimized版本之前使用printf显示实现。

 void main() {
   int count=0;
   int nmax;       
   char num[9]={0};
   int n;
   printf( "Insert number1 ");
   scanf( "%d", &nm );
   printf( "Insert number2 ");
   scanf( "%d", &nmax );
   while( nm <= nmax ) {
       int sumup=0, sumdown=0;
       sprintf( num, "%d", nm );
       for( n=0; n<4; n++ ) {
         sumup += num[n] -'0'; // subtracting '0' is not necessary (see below)
         sumdown += num[7-n]-'0';  // subtracting '0' is not necessary (see below)
       }
       if( sumup == sumdown ) {
       /* whatever */
          count++;
       }
       nm++;
   }
 }

在使用strtol调用for循环和字符串长度之前,您可能需要使用strlen检查字符串是否为有效数字。我在这里设置了你需要的固定值(我假设长度总是8)。

所示算法的缺点是任何可能做得更糟的循环的sprintf ...所以我们应用了两个主要的变化

  1. 我们使用[0-9]而不是['0';'9']
  2. 我们删除了sprintf以获得更快的解决方案,考虑到我们需要格式化从前一个数字开始的数字字符串(n-1)
  3. 最后,伪优化算法应该类似于下面所示的算法,其中删除了所有分区和模块(除第一个数字外),并使用字节而不是ASCII。

    void pseudo_optimized() {
       int count=0;
       int nmax,nm;       
       char num[9]={0};
       int sumup=0, sumdown=0;
       int n,i;
       printf( "Insert number1 ");
       scanf( "%d", &nm );
       printf( "Insert number2 ");
       scanf( "%d", &nmax );
       n = nm;
       for( i=7; i>=0; i-- ) {
          num[i]=n%10;
          n/=10;
       }
       while( nm <= nmax ) {
    
           sumup = num[0] + num[1] + num[2] + num[3];
           sumdown = num[7] + num[6] + num[5] + num[4];
           if( sumup == sumdown ) {
           /* whatever */
              count++;
           }
           nm++;
           /* Following loop is a faster sprintf replacement and
            * it will exit at the first value 9 times on 10
            */
            for( i=7; i>=0; i-- ) {
             if( num[i] == 9 ) {
                num[i]=0;
             } else {
                num[i] += 1;
                break;
             }
           }
       }
    }
    

    我的虚拟对象 5.500000 s 的原始算法,此算法 0.950000 s 已针对[00000000 =&gt;进行了测试99999999] 这个算法的弱点是它使用了数字之和(这是不必要的和一个可以展开的...循环。

    *更新* 进一步优化。数字的总和不是必需的....考虑一下我可以通过以下方式改进算法:

    int optimized() {
       int nmax=99999999,
       int nm=0;
       clock_t time1, time2;
       char num[9]={0};
       int sumup=0, sumdown=0;
       int n,i;
       int count=0;
       n = nm;
       time1 = clock();
       for( i=7; i>=0; i-- ) {
          num[i]=n%10;
          n/=10;
       }
       sumup = num[0] + num[1] + num[2] + num[3];
       sumdown = num[7] + num[6] + num[5] + num[4];
       while( nm <= nmax ) {
          if( sumup == sumdown ) {
             count++;
          }
          nm++;
          for( i=7; i>=0; i-- ) {
             if( num[i] == 9 ) {
                num[i]=0;
                if( i>3 )
                   sumdown-=9;
                else
                   sumup-=9;
         } else {
                num[i] += 1;
                if( i>3 )
                  sumdown++;
                else
                  sumup++;
                break;
             }
          }
       }
       time2 = clock();
       printf( "Final-now %d %f\n", count, ((float)time2 - (float)time1) / 1000000);
       return 0;
    }
    

    我们到达 0.760000 s ,这比使用查找表在同一台机器上获得的结果慢3倍。

    * update * 优化和展开:

    int optimized_unrolled(int nm, int nmax) {
       char num[9]={0};
       int sumup=0, sumdown=0;
       int n,i;
       int count=0;
       n = nm;
       for( i=7; i>=0; i-- ) {
          num[i]=n%10;
          n/=10;
       }
       sumup = num[0] + num[1] + num[2] + num[3];
       sumdown = num[7] + num[6] + num[5] + num[4];
       while( nm <= nmax ) {
          if( sumup == sumdown ) {
             count++;
       }
       nm++;
       if( num[7] == 9 ) {
          num[7]=0;
          if( num[6] == 9 ) {
             num[6]=0;
             if( num[5] == 9 ) {
                num[5]=0;
                if( num[4] == 9 ) {
                   num[4]=0;
                   sumdown=0;
                   if( num[3] == 9 ) {
                      num[3]=0;
                      if( num[2] == 9 ) {
                         num[2]=0;
                         if( num[1] == 9 ) {
                            num[1]=0;
                            num[0]++;
                            sumup-=26;
                         } else {
                            num[1]++;
                            sumup-=17;
                         }
                      } else {
                         num[2]++;
                         sumup-=8;
                      }
                   } else {
                      num[3]++;
                      sumup++;
                   }
                } else {
                   num[4]++;
                   sumdown-=26;
                }
            } else {
               num[5]++;
               sumdown-=17;
            }
         } else {
            num[6]++;
            sumdown-=8;
         }
      } else {
         num[7]++;
         sumdown++;
      }
    }
    return count;
    }
    

    展开向量可提高约50%的速度。算法成本 0.36000 s ,因为它比前一个解决方案更多地使用堆栈(因为某些'if'语句可能会导致推送,所以它不能总是使用)。结果与同一台机器上的Alg2 @ Michael Burr相当,[Alg3-Alg5] @Michael Burr在堆栈不受关注的情况下要快得多。

    注意 所有在intel VMS上执行的测试。如果我有空的话,我会尝试在ARM设备上运行所有那些算法。

答案 4 :(得分:1)

#include <stdio.h>

int main(){
    int M, N;

    scanf("%d", &M);
    scanf("%d", &N);

    static int table[10000] = {0,1,2,3,4,5,6,7,8,9};
    {
        register int i=0,i1,i2,i3,i4;
        for(i1=0;i1<10;++i1)
        for(i2=0;i2<10;++i2)
        for(i3=0;i3<10;++i3)
        for(i4=0;i4<10;++i4)
            table[i++]=table[i1]+table[i2]+table[i3]+table[i4];
    }
    register int cnt = M, second4 = M % 10000;
    int res = 0, first4 = M / 10000, sum1=table[first4];
    for(; cnt <= N; ++cnt){
        if(sum1 == table[second4])
            ++res;
        if(++second4>9999){
            second4 -=10000;
            if(++first4>9999)break;
            sum1 = table[first4];
        }
    }
    printf("%d", res);
    return 0;
}

答案 5 :(得分:0)

如果您知道数字是这样固定的,那么您可以通过子串函数来获取组件并进行比较。否则,您的调制器操作会造成不必要的时间。

答案 6 :(得分:0)

我找到了更快的算法:

#include <stdio.h>
#include <ctime>

int main()
{
    clock_t time1, time2;
    int M, N, res = 0, cnt, first4, second4, sum1, sum2,last4_ofM,first4_ofM,last4_ofN,first4_ofN,j;

    scanf("%d", &M);
    scanf("%d", &N);

    time1 = clock();


    for(cnt = M; cnt <= N; cnt++)
    {
        first4 = cnt % 10000;
        sum1 = first4 % 10 + (first4 / 10) % 10 + (first4 / 100) % 10 + (first4 / 1000) % 10;
        second4 = cnt / 10000;
        sum2 = second4 % 10 + (second4 / 10) % 10 + (second4 / 100) % 10 + (second4 / 1000) % 10;

        if(sum1 == sum2)
            res++;

    }
    time2 = clock();
    printf("%d\n", res);
    printf("first algorithm time: %f\n",((float)time2 - (float)time1) / 1000000.0F );
    res=0;

    time1 = clock();
    first4_ofM = M / 10000;
    last4_ofM = M % 10000;
    first4_ofN = N / 10000;
    last4_ofN = N % 10000;

    for(int i = first4_ofM; i <= first4_ofN; i++)
    {
        sum1 = i % 10 + (i / 10) % 10 + (i / 100) % 10 + (i / 1000) % 10;

        if ( i == first4_ofM ) 
            j = last4_ofM;
        else 
            j = 0;
        while ( j <= 9999)
        {
            sum2 = j % 10 + (j / 10) % 10 + (j / 100) % 10 + (j / 1000) % 10;
            if(sum1 == sum2)
                res++;
            if ( i == first4_ofN && j == last4_ofN ) break;
            j++;
        }

    }
    time2 = clock();
        printf("%d\n", res);
        printf("second algorithm time: %f\n",((float)time2 - (float)time1) / 1000000.0F );
    return 0;
}

我不需要一直计算前四位数的总和。我需要每10000次迭代计算一次。在最坏的情况下,输出是:

10000000
99999999
4379055
first algorithm time: 5.160000
4379055
second algorithm time: 2.240000

大约一半更好的结果。