zoukankan      html  css  js  c++  java
  • 算法的力量

     

    有这么一个数,当把它的最后一位(个位)挪到第一位的时候,得到的新数刚好是原来数的两倍。问这个数是多少?

    ——出自 1985 出版的一本小学5年级学生用的数学课外读物——《儿童数学世界》

    这个问题看似简单,就是要找一个数出来,把这个数个位上的数字挪到最前面去,例如 123 变成 312,12345变成51234。但是还要求得到的“新数”要是原来数的两倍。

    简单的分析一下这条业务规则,不难得出下面的结论:

    1.       取一个数作为“原数”;

    2.       把“原数”个位上的数字挪到最前面,保存为一个“新数”;

    3.       比较两个数字,如果“新数”是“原数”的两倍,则打印两个数并退出程序;

    4.       如果不符合要求,则原数自加1并回到步骤2。

    显然通过手工方式找到这个数是不太现实的,为了加快查找这个数的速度,让我们编写一段代码来提高工作效率。已经实现的代码如下:

    1#~ defined a method to move the last number to line-begin
    2
    3def get_new_number(original_number)
    4
    5
    6
    7 #~ get last number
    8
    9 last_number = original_number%10
    10
    11 puts "the last_number is : "+last_number.to_s
    12
    13
    14
    15 #~ get the length of original_number
    16
    17 original_number_in_sting=original_number.to_s
    18
    19 puts "the length of original_number is : "+original_number_in_sting.length.to_s
    20
    21
    22
    23 #~ set the original_number = original_number/10
    24
    25 original_number = original_number/10
    26
    27 puts "the new original_number is : "+original_number.to_s
    28
    29
    30
    31 #~ move the last number to line-begin of original_number
    32
    33 for counter in 2..original_number_in_sting.length
    34
    35 last_number=last_number*10
    36
    37 end
    38
    39 puts "the new last_number is : " + last_number.to_s
    40
    41
    42
    43 #~ return the new number
    44
    45 return last_number+original_number
    46
    47
    48
    49end
    50
    51
    52
    53
    54
    55#~ initialization
    56
    57#~ set the variable original_number = a number that the number >11
    58
    59original_number = 1
    60
    61puts "First: the original_number is : " + original_number.to_s
    62
    63#~ set the variable new_number = get_new_number(number01)
    64
    65new_number = get_new_number(original_number)
    66
    67puts "First: the new_number is : "+new_number.to_s
    68
    69#~ finished initialization
    70
    71
    72
    73
    74
    75while original_number*2 != new_number
    76
    77
    78
    79 original_number = original_number + 1
    80
    81 puts "the original_number in loop is : "+original_number.to_s
    82
    83
    84
    85 new_number = get_new_number(original_number)
    86
    87 puts "the new_number in loop is : "+new_number.to_s
    88
    89
    90
    91end
    92
    93
    94
    95puts "We’ve got the number! It is : "+original_number.to_s

    上面的代码是在 Ruby 1.8.4 下面调试通过的。这是典型的完全通过分析业务规则并忠实于业务规则而实现的一段代码,其中定义了一个方法专门来处理“把个位的数字挪到最前面生成一个新数”这件事情,其他部分就是不断地反复比较、尝试,直到找到我们所期望的那个数。这段代码也并不复杂,其中“#~ ”表示注释掉的内容,puts 表示打印信息在屏幕上。如果你有兴趣可以很容易的用其他语言改写。

    如果说上面的分析和代码实现是使用了“业务视角”的话,下面我们再换个视角看看。

    根据业务规则——有这么一个数,当把它的最后一位(个位)挪到第一位的时候,得到的新数刚好是原来数的两倍——我们可以知道,这个数至少是两位以上的,并且可以人为的分为两个部分——个位部分和其他部分,可以用一个等式来表示这条业务规则想表达的意思:

    2*(10X+Y) = Y*10 (n-1) + X

    让我们继续化简这个等式:

    20X+2Y = Y*10 (n-1) + X

    19X = Y*10 (n-1) – 2Y

    19X = Y (10 (n-1) -2)

    最终我们得到了下面这个等式

    X = Y (10 (n-1) -2)/19

    在上面的等式中,Y表示个位上的数字,X表示其余的部分,n表示这个数的位数,例如:对于123这个数,Y=3,X=12,n=3。我们可以知道X和Y一定都是正整数,另外,我们还可以知道Y一定是一个0-9之间的数字,所以我们只要求得X的值,就很容易的可以知道我们要找的那个数了。

    最后一个关键,就是n的取值,但其实这个值我们是可以控制的,我们可以尝试着给n赋一个值,然后把10 (n-1) 作为一个值来处理。如果在一个既定的范围内找不到我们需要的数,我们可以继续加大n的取值。这样在我们的等式中就只剩下X这一个未知数了。

    最终我们得到下面的代码:

    1 n = 1
    2
    3 #~ 计算10 的100次方内是否有我们要找的数
    4
    5 for i in 1..100
    6
    7 n =n * 10
    8
    9 for y in 1..9
    10
    11 #~ 如果找到了我们需要的数,就打印出“原数”和“新数”
    12
    13 if (y*(n-2))%19==0 then
    14
    15 puts "the original number is : " + (10*((y*(n-2))/19)+y).to_s
    16
    17 puts "the new number is : " + (2*(10*((y*(n-2))/19)+y)).to_s
    18
    19 end
    20
    21 end
    22
    23 end
    24

    执行这段代码后,我们获得了下面这些返回结果:

    the original number is      : 52631578947368421

    the new number is              : 105263157894736842

    the original number is      : 105263157894736842

    the new number is              : 210526315789473684

    the original number is      : 157894736842105263

    the new number is              : 315789473684210526

    the original number is      : 210526315789473684

    the new number is              : 421052631578947368

    the original number is      : 263157894736842105

    the new number is              : 526315789473684210

    the original number is      : 315789473684210526

    the new number is              : 631578947368421052

    the original number is      : 368421052631578947

    the new number is              : 736842105263157894

    the original number is      : 421052631578947368

    the new number is              : 842105263157894736

    the original number is      : 473684210526315789

    the new number is              : 947368421052631578

    从中我们可以看到,除了标为红色的第一组以外,其他的数都是符合我们要求的。

    前面写了这么大堆,当然目的还是想说明一下这两种方法的差别。

    第一种方法是完全面向业务的分析和实现方法,代码并不算累赘,而且很容易通过阅读代码反向来了解业务的原始需求和业务规则;而后一种则是通过数学的方法进行分析和抽象之后得到的结果。相比较之下,相信大家不能看出两者之间执行效率上的差距——因为第一种方法的原理是从1开始逐个尝试。

    我就不再计算圈复杂度或者执行效率之类的刻板数据了,让我们用一个更直观的方法来对比一下这两种算法的差别。

    第一种方法是我最开始的做法,那段代码看似中规中矩,但是通过最后得到的结果我们可以看到,符合我们要求的最小的一个数也大于10的17次方,而使用我的方法在一台P4 3G + 1G 内存的机器上运行了一小时也不过才尝试到10的9次方,照此计算,至少要连续运行10多万年才能找到第一个符合要求的数字。

    而第二种方法,则是得益于QQ群里一位昵称为“岚”的朋友的指点,使用这个方法,一秒中已经可以完成10的100次方以内的查找。

     

    1 vs. 10万年!


       这就是算法的力量!这就是知识的价值!

    如果你还在用代码描述着业务,那么尝试一下第二种方法吧 ^_^

  • 相关阅读:
    (error) DENIED Redis is running in protected mode because protected mode is enabled
    boost库安装和使用
    linux下Redis以及c++操作
    Redis 客户端安装与远程连接图解
    Redis 安装和配置
    terminate called after throwing an instance of 'std::out_of_range' what(): basic_string::substr
    C++ STL std::wstring_convert处理UTF8
    C++正确的cin输入
    分词之最短编辑距离算法实现(包括中文)
    unicode和utf-8互转
  • 原文地址:https://www.cnblogs.com/AkQuan/p/2292767.html
Copyright © 2011-2022 走看看