一直很习惯用java,但渐渐发现大神们都在用C++/C,在ACM中AC列表中的时间显著地体现了C++/C的速度优势。但今天恍然大悟,语言 doesn't matter, 算法 doesn‘t matter ,这些都是工具,数学才是最令人敬畏的。
看似复杂的问题背后,苛刻的复杂度要求下,若能看破数字的奥秘,规律的实质,一切问题将迎刃而解。
举个例子来讲,今天遇到了一道网易笔试题——最大奇约数
小易是一个数论爱好者,并且对于一个数的奇数约数十分感兴趣。一天小易遇到这样一个问题: 定义函数f(x)为x最大的奇数约数,x为正整数。 例如:f(44) = 11.
现在给出一个N,需要求出 f(1) + f(2) + f(3).......f(N)
例如: N = 7
f(1) + f(2) + f(3) + f(4) + f(5) + f(6) + f(7) = 1 + 1 + 3 + 1 + 5 + 3 + 7 = 21
小易计算这个问题遇到了困难,需要你来设计一个算法帮助他。
先放上代码,我们再来解释。
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n = sc.nextLong();
long sum = 0;
for(long i =n;i>0;i/=2){
long temp = (i+1)/2;
sum+= temp*temp;
}
System.out.println(sum);
}
}
我们不难发现,奇数的最大奇约数就是它自身。关键在于偶数。 对于偶数我们怎么处理呢?我们要用此偶数除以2,商若为奇数,那么我们得到了它的最大奇约数,若商为偶数,那么继续除以2,一直到商为奇数为止。
用一组数字来说明,当N为10 时,我们得到原始数列 1 2 3 4 5 6 7 8 9 10
现在 i= 10;
将 1 3 5 7 9 挑出来, 我们还剩 2 4 6 8 10这5个偶数
现在 i = 5;
那么我们得到 1 2 3 45 ,这里面的 1 3 5 恰是 剩下的元素中 1 6 10 的最大奇约数。我们还剩2 4 这2个偶数
现在 i = 2;
对于偶数我们继续除以2 得到 1 2 ,这里面的1 恰是 剩下的元素中 2 的最大奇约数,我们还剩下 2这 1一个偶数
现在 i = 1;
对于偶数我们依旧除以2 得到1 好了,现在我们得到的序列没有偶数了,这个1 就是剩下的元素中 2 的最大奇约数。就可以到此结束了。(这里可以看到,8先是除以42得到4,但4是偶数,于是4再除以2得到 2,但2是偶数,所以2再继续除以2,得到1 ,到了奇数才结束)
现在我们总览全局,每一步我们都会挑出序列中的奇数来,那么挑出来的奇数必然是最后所有奇约数中的,既然要求所有的奇约数的和,那么我们为何不把每次挑出来的奇约数的和先求出来呢?
那么每次挑出来的奇数的和与每次的i有什么关系呢? 我们可以看出每次挑出的奇数的和是 <=i的奇数的和哎! 那这样就好办了!等差数列求和公式走起!
当i是奇数的时候,利用求和公式 sum = (1 + i )/2*(1+ i )/2
当i是偶数的时候,利用求和公式 sum = (1 + i -1)*i /2 ,即 i* i /2,那么这个式子 是不是等于 ((1+i)/2) * ((1+i)/2) 呢? 答案是肯定的,因为 i是偶数啊,i+1后在除以2 是要强制取整的。 这样,我们就将i是奇数和偶数两种情况的计算式统一了起来。
这样,我们在用算法表达式将思路实现出来就不难了吧!