Non-abundant sums
Problem 23
A perfect number is a number for which the sum of its proper divisors is exactly equal to the number. For example, the sum of the proper divisors of 28 would be 1 + 2 + 4 + 7 + 14 = 28, which means that 28 is a perfect number.
A number n is called deficient if the sum of its proper divisors is less than
n and it is called abundant if this sum exceeds n.
As 12 is the smallest abundant number, 1 + 2 + 3 + 4 + 6 = 16, the smallest number that can be written as the sum of two abundant numbers is 24. By mathematical analysis, it can be shown that all integers greater than 28123 can be written as the sum of two
abundant numbers. However, this upper limit cannot be reduced any further by analysis even though it is known that the greatest number that cannot be expressed as the sum of two abundant numbers is less than this limit.
Find the sum of all the positive integers which cannot be written as the sum of two abundant numbers.
翻译(大意):
如果一个数 n,它的所有真因子之和如果等于n,n就被称为完全数(例如28,1+2+4+7+14=28,所以28为完全数)。小于n,n被称为不足数。大于n,能被称为过剩数。最小的过剩数为12,(1+2+3+4+6=16, 大于12),所以最小的两个过剩数之和为12+12=24。
经过数学分析,所有大于28123的数都可以写成两个过剩数之和。求出正整数中不能被写成两个过剩数之和的所有数之和。
解决方案:
根据题意,大于28123的数所有数都能被写成两个过剩数之和,所有我们只需要关注小于等于28123的数。我的思路是:step1:求出所有小于28124的过剩数, step2:写一个函数,输入参数为n,算所有过剩数中,是否有两个的和可以等于n, step3:根据step2的返回值,累加结果。思路还是很简单,我们来看一看代码。
代码:
package projectEuler; import java.util.ArrayList; public class Problem23 { private static ArrayList<Integer> list; private static final int SIZE = 28123; public static void main(String[] args) { long start = System.currentTimeMillis(); list = initAbundantList(); int result = 0; for (int i = 1; i <= SIZE; i++) { result += (ifCanBeTheSumOfTwoAbundantNumber(i) ? 0 : i); } System.out.println("result:" + result); long end = System.currentTimeMillis(); System.out.println("time:" + (end - start)); } /* * 判断过剩数列表中是否有两个数之和等于num */ private static boolean ifCanBeTheSumOfTwoAbundantNumber(int num) { int duration = findFirstIndexGreaterThanNum(num); for (int i = 0; i <= duration; i++) { for (int j = 0; j <= duration; j++) { if (num == (list.get(i) + list.get(j))) { // System.out.print("" + list.get(i) + "," + list.get(j)); return true; } } } return false; } /* * 在过剩数列表中,找到小于等于num的最后一个索引 */ private static int findFirstIndexGreaterThanNum(int num) { int lengh = list.size(); int i = 0; while (i < (lengh - 1) && list.get(i) < num) { i++; } return i; } /* * 初始化小于等于28123的过剩数列表 */ private static ArrayList<Integer> initAbundantList() { ArrayList<Integer> list = new ArrayList<Integer>(); for (int i = 1; i <= SIZE; i++) { if (countSumProperDivisor(i) > i) { list.add(i); } } return list; } /* * 算num的所有真因子的和,方法一 */ private static int countSumProperDivisor(int num) { int sum = 0; int sqrtNum = (int) Math.sqrt(num); for (int i = 2; i <= sqrtNum; i++) { if (0 == num % i) { if (i == num / i) { sum += i; } else { sum += (i + num / i); } } } return sum + 1; } /* * 算num的所有真因子的和,方法二 */ private static int countSumProperDivisor2(int num) { int sum = 0; int duration = (int) num / 2; for (int i = 1; i <= duration; i++) { if (0 == num % i) { sum += i; } } return sum; } }
代码中有注释,就不多做解释。思路和上面的解题思路是一样的。我这里还分析几个函数。
求一个数的所有真因子之和这里提供了两种方法:
求真因子之和:
方法一:这种方法只需遍历 2到 sqrt(num) 之间的数,1是能被所有数整除,所以可以直接在后面加1,我们就可以直接从2开始算。 每求到一个数的时候,其实是一对,比如24, 24/2 = 12,所以这里我们求得的是2个数 2和12,4和6是一对,而且可以发现规律,较小的数都是小于 sqrt(num)的,所有较大的数都大于 sqrt(num) ,所以我们才能这样算。但是需要注意,比如25, 最后算到5*5=25的时候,我们不能加两个数,而是应该直接加一个5。好了,这里就分析完了这个思路。
/* * 算num的所有真因子的和,方法一 */ private static int countSumProperDivisor(int num) { int sum = 0; int sqrtNum = (int) Math.sqrt(num); for (int i = 2; i <= sqrtNum; i++) { if (0 == num % i) { if (i == num / i) { sum += i; } else { sum += (i + num / i); } } } return sum + 1; }
方法二:第二种方法要费时一点,从2到num/2计算,因为 大于num/2的所有数都不可能是num的真因子(想一下就知道)。所以这里把小于等于num/2的部分都算一遍,判断是否能mod尽就OK了。
/* * 算num的所有真因子的和,方法二 */ private static int countSumProperDivisor2(int num) { int sum = 0; int duration = (int) num / 2; for (int i = 1; i <= duration; i++) { if (0 == num % i) { sum += i; } } return sum; }
求过剩数列表很简单就不讲了,我们再着重讲一下怎么判断是否有两个过剩数之和等于输入参数。这里我可以先提供一种思路,就是我们可以把列表中的所有过剩数之和能组合成的和算出来保存在数组中,后面就只需要将每个数遍历一次这个列表就可以知道。但是这个方法难在要求所有过剩数列中任意两个数之和,太耗费时间。所以来讲一下我的思路:
每传入一个数num,我们就在现有的过剩数列表中遍历任意两个数之和,一旦等于num,结束循环,返回true。 这里遍历的范围是提高速度的关键,我们可以思考一下,其实要两个过剩数之和等于num,那么过剩数肯定都小于num(过剩数中没有0),所以后面写了一个函数findFirstIndexGreaterThanNum(int num) ,根据名字就知道,实在过剩数列中找到小于num的范围,然后我们在判断 ifCanBeTheSumOfTwoAbundantNumber(int num)就可以缩小范围。不要小瞧这点范围哦,可以节约将近一半的计算次数。下面是代码:
/* * 判断过剩数列表中是否有两个数之和等于num */ private static boolean ifCanBeTheSumOfTwoAbundantNumber(int num) { int duration = findFirstIndexGreaterThanNum(num); for (int i = 0; i <= duration; i++) { for (int j = 0; j <= duration; j++) { if (num == (list.get(i) + list.get(j))) { // System.out.print("" + list.get(i) + "," + list.get(j)); return true; } } } return false; } /* * 在过剩数列表中,找到小于等于num的最后一个索引 */ private static int findFirstIndexGreaterThanNum(int num) { int lengh = list.size(); int i = 0; while (i < (lengh - 1) && list.get(i) < num) { i++; } return i; }
好了,写完了,结果是:
result:4179871
time:14711
用时将近15秒,还是挺慢的,希望能和大家交流哦。