我必须找到前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;
}
我正在努力寻找一种更有效的方法来实现这一目标。
答案 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个。
此算法的以下实现
此算法以比原始实现方式更快的方式运行(对于足够大的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
...所以我们应用了两个主要的变化
最后,伪优化算法应该类似于下面所示的算法,其中删除了所有分区和模块(除第一个数字外),并使用字节而不是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
大约一半更好的结果。