Project Eluer - 14

问题:

Longest Collatz sequence

Problem 14

The following iterative sequence is defined for the set of positive integers:

n → n/2 (n is even)
n → 3n + 1 (n is odd)

Using the rule above and starting with 13, we generate the following sequence:

13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1

It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.

Which starting number, under one million, produces the longest chain?

NOTE: Once the chain starts the terms are allowed to go above one million.

翻译:

首先是两个公式:

n = n/2 (如果n是偶数)

n = 3n+1(如果n是奇数)

根据这两个公式,如果n从13开始,那么可以得到序列: 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1,序列中一共包括10个数。 这两个公式虽然还未经证明,但是经过试验,一定可以回到1。所以现在我们要求1000000(million)以下,序列长度最长的数是多少?

思考:

咋一看这道题很简单,只需要一个函数能得到从当前数到1序列的长度,然后从1开始遍历到1000000就可以了。当然,你可以试一试,如果是100以下会很快算出答案。进入主题:当然1到1000000这一百万个数我们是需要遍历的,但是如果每次都从开始算到1那会走很多重复算很多无用的数字。比如:1开始,直接return 1, 那么2的话,第一次2/2之后得到1,但是以1为起点的序列次数我们是知道的(因为第一步就是算得1),所以我们可以把当前算得次数加上1的序列长度就是2的序列长度。再例如算8的时候,我们知道序列是:8->4->2->1,当我们第一步8/2=4的时候,我们应该意识到4的序列长度4->2->1肯定等于3,使我们之前算过的,所以8的序列长度应该是1+3=4。这样我们就可以利用前面算过的数的序列长度,加上原数到这个数的序列长度,求得结果,可以节约非常大的开销。这是我的办法,现在贴上代码:

package projectEuler;

public class Problem14 {
	private static final int MAX = 1000001;
	public static void main(String[] args){
		System.out.println(getResult());
	}
	
	public static long getResult(){
		int[] arr = new int[MAX];
		int[] result;
		for(int i=1; i<arr.length; i++){
			result = caculate(i);
			if(result[0]!=0){
				arr[i] = result[1]+arr[result[0]];
			}
			else{
				arr[i] = result[1];
			}
		}
		int max = arr[1];
		int maxIndex = 1;
		for(int i=1; i<arr.length; i++){
			if( max<arr[i] ){
				max = arr[i];
				maxIndex = i;
			}
		}
		return maxIndex;
	}
	
	public static  int[] caculate(int index){
		long temp = index;
		int[] result = new int[2];
		result[1] = 0;
		while(temp != 1 && temp>=index){
			if(isOdd(temp))
				temp = divide2(temp);
			else
				temp = mutil3add1(temp);
			result[1]++;
		}
		result[1]++;
		if(temp < index){
			result[0] = (int)temp;
			result[1]--;
		}
		return result;
	}
	
	public static long divide2(long n){
		return n/2;
	}
	
	public static long mutil3add1(long n){
		return 3*n+1;
	}
	
	public static boolean isOdd(long n) {
		if (n % 2 == 0) {
			return true;
		}
		return false;
	}
	
}


代码有点丑陋,所以还是做一下解释:

首先是 divide2() 和 mutil3add1()这两个函数是那两个公式,不用解释。接着有一个函数叫 caculate() (抱歉没有想到更好的名字),它的作用就是得到index到第一个比index小的数temp的之间序列长度,返回值是一个int[] 数组,int[0]为第一个比index小的数temp,int[1]为index到temp之间的序列长度,之所以要返回int[0]是因为我用的数据结构,因为我们要算1到1000000之间所有数通过公式到达1之间的序列长度,那就需要1000000的数组来存储每个数的序列长度,数组的下标从1到1000000,下标表示要求的数,值为序列长度。那么通过从1开始遍历,我会依次得到以1开始的序列长,2开始的序列长,3开始的序列长,...当我算到4开始的序列长时, 在caculate()中,算过第一次4/2之后得到temp=2,那么我知道这个时候就应该结束了,因为以2开始的序列长度我是知道的,只要加上我现在算过的长度就可以了,依次类推。

有点啰嗦,也不知道讲明白没有,谅解!

你可能感兴趣的:(算法,欧拉项目)