有道难题

有道难题的初赛已经结束了,贫道也第一次玩了一下这种测试,刚做完时很兴奋,成绩却很不好。有道难题看似简单,其实不易,经此一测,贫道发现自己离大道还甚远。
题目:


请注意,在上面的伪代码中,A[i]和B[i]分别表示字符串A和B中下标为i的字符(下标编号从0开始)。对“完全平方数”的定义是,对于整数i,存在整数j,使得i= j *j,则称i为完全平方数。
下面具体说明序列生成的过程:如果n=7,则在每一轮迭代中,A的取值依次为:0, 01, 0110, 01101010,所以最终产生的二进制序列就是0,1,1,0,1,0,1,0
请返回上述序列中下标为n的数字(该序列的下标从0开始)
类及函数定义:
Class:BinarySequence
Method signature:int getValue(int n)
(be sure your method is public)
要求:
n 的取值大于等于0,小于等于2,000,000,000
输入->输出:7->0  2->1  8->1  11->0  10->1  15->0
开解:
贫道刚看到此题,窃喜不已:何等简单啊,算法的伪码都给出来了,简直就是不烦脑细胞吧。于是贫道十指连发,打下了如下爪哇咒语:


再拿要求里的输入输出一验证,无量天尊,貌似是对的;藐视之余,贫道拿分后飘然而去......
直到第二天被板砖拍回来。
据说系统拿2,000,000,000来测试贫道的程序,由于举办方机器不够强劲,所以没有通过;鉴于解释权在彼方,贫道只能捏着牛鼻子配合对程序进行优化优化。
既然是伪码的一致性实现,那么正确率应该问题不大,那先看一下程序的效率吧:
输入(耗时):2000(15)  20000(1704) 40000(6859)  80000(41750) 200000(zzZZZZZZZZZZZ)

 

看来程序确实有那么点问题,要动动手术才行。
修改1:判断是否完全平方数的函数isSquare实在太圡了,听说可以使用系统函数,自然想到了Java的Math类。

再看看效率:2000(16) 20000(750) 40000(3812) 80000(28188) 200000(240703) 2000000(zZZZZZZZZZZZZZ);虽然提高了一倍,但还是不尽人意。
Math.sqrt()还能优化吗,找找Java的源代码,Math.sqrt调用的是StrictMath.sqrt(),而后者是一个native方法,也就是说用其他语言在底层实现了,那就先这样吧。

修改2:看看伪码的实现getValue函数吧,中间有频繁的字符串变更,而String对象又是不可变的,那么显然用StringBuffer会好一些吧。把A和B都改成StringBuffer类型再看看效率:
2000(0) 20000(15) 40000(32) 80000(63) 200000(93) 2000000(672),5000000(2656).......不错,效率提高了数个量级,但是再增大n,取到10000000的时候,贫道的Eclipse报了一个OutOfMemoryError;一咬牙把运行参数上加了一个 -mx800M n能增大一些,但是大不了多少就又zZZZZZZZZZZZZZZZZZZZZ,三清祖师,看来还需要再优化。

修改3:再优化一下伪码算法的实现,考虑减少计算,提高已有计算结果的复用度。
已知:如果n=7,则在每一轮迭代中,A的取值依次为:0, 01, 0110, 01101010。
每一次迭代都是从A的第一个字符算起,A的前一半字符在上一次迭代中都计算过了,并且把计算的结果B追加在了A的后面,也就是说:上n次结果中的B之和在本次中可以复用(直接追加),能够减少一半的计算量。
在这种想法下,修改getValue,得到如下代码:

再来看看效率:2000(0) 20000(0) 40000(15) 80000(16) 200000(31) 2000000(344),5000000(641),10000000(1265)。确实提高了一倍,也突破了10000000这个坎,可再增大n至20000000时又报了OutOfMemoryError。StringBuffer虽然没有长度限制,但是其大小也是与jvm相关的,太大了会内存不够。看来得在减少StringBuffer长度上做些文章了。
修改4:回头再看看伪码和要求,最后要求输出的是第n位字符而不是全部,那么可否直接算n而忽略中间过程呢,n又有什么规律呢?
n=0 输出0
n=1 输出1
n=2,4,8.......由于字符串每次都是成倍增长,所以当n为2的幂时,必然是由第一个字符'0'生成,也就是说输出一定是1
如果n不是2的幂时,它会夹在两个2的幂中间,设为k<n<m,那么n就是由位于(n-k)的字符变化而来,所以可以先得到(n-k),再取n。
于是将getValue写成了这样的形式:

public int getValue(int n)仍旧采用前面的。效率提高了:2000000(203) 20000000(453)
特别是如果n在k的附近,那就非常快了,但是n远离k的时候呢,(n-k)有可能还是上千万,就还是会陷入getValue的OutOfMemoryError。
修改5:接前面的思路,必须减少(n-k)的计算量,把那n-k看做一个单独的数a来计算,与n类似,a岂不是又可以用两个2的幂来来计算?一层层迭代,这是什么?对了,迭代。
于是修改getvalue为迭代函数:

看看效率:20000000(0) 2000000000(0),非常强大...
原来这就是道啊......
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值