欧拉计划

欧拉计划

Problem 1Multiples of 3 and 5

1.题干

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.q

2.求解

/*3或5的倍数
在小于10的自然数中,或的倍数有3、5、6和9,这些数之和是23。
求小于1000的自然数中所有3或5的倍数之和。
* */
public class _01Multiples_of_3_and_5 {
    public static void main(String[] args) {
        //        System.out.println(countSum(10));
        System.out.println(countSum(1000));
        int res = getSum(1000);
        System.out.println(res);

        int res2 = sumDivisibleBy(3,999)+sumDivisibleBy(5,999)-sumDivisibleBy(15,999);
        System.out.println(res2);
    }
    //暴力破解1
    public static int countSum(int n){
        int sum = 0;
        for (int i = 3; i < n; i++) {
            if(i%3 == 0){
                sum=sum+i;
            }
            if(i%5 ==0){
                sum=sum+i;
                if (i%3 == 0){
                    sum=sum-i;
                }
            }
        }
        return sum;
    }
    //    暴力破解2
    public static  int getSum(int n){
        int sum = 0;
        for (int i = 0; i < n; i++) {
            if (i%3 == 0||i%5==0){
                sum = sum + i;
            }
        }
        return sum;
    }
    //观察规律可以看出来,单独计算1000%3的+1000%5的,这样我们会计算两遍能整除15的,所以需要减掉
    //3+6+9+12+...+996+999 = 3*(1+2+3+...+333) = 3*1/2*(333+1)*333

    public static  int sumDivisibleBy(int n,int target){
        int p = target / n;
        return n*(p*(p+1))/2;
    }

}

image-20211102091612909

答案 = {所有三的倍数的数的和} + {所有5的倍数的数的和} - {所有15的倍数的数的和}

3.总结

1.等差数列的前n项和

image-20211101112013987

Problem 2Even Fibonacci numbers

1.题干

Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:

1,2,3,5,8,13,21,34,55,89,…

By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.

偶斐波那契数

斐波那契数列中的每一项都是前两项的和。由1和2开始生成的斐波那契数列的前10项为:

1,2,3,5,8,13,21,34,55,89,…

考虑该斐波那契数列中不超过四百万的项,求其中为偶数的项之和。

2.求解

1.直接求解:

    //        1.直接法:
    public static void solution1() {

        int limit = 4000000;
        int sum = 0;
        int a = 1;
        int b = 1;
        int temp = 0;
        while (b < limit) {
            if (b % 2 == 0) {
                sum = sum + b;
            }
            temp = a + b;
            a = b;
            b = temp;
        }
        System.out.println(sum);
    }

image-20211102192659940

2.观察规律

观察一下,发现这里斐波那契数列中偶数项都是三的倍数

所以可以直接每三个加一次

//2.每三项加一下
public static void solution2() {
    int limit = 4000000;
    int a = 1;
    int b = 1;
    int c = a + b;
    int sum = 0;
    while (c < limit) {
        sum = sum + c;
        a = b + c;
        b = c + a;
        c = a + b;
    }
    System.out.println(sum);
}

3.这里还有另外一种规律:

如果我们只写偶数:

image-20211102193656996

可以看出他们似乎遵守一个递归关系

E(n) = 4*E(n-1)+E(n-2)

我们可以证明斐波那契数列遵循F(n) = 4F(n-3)+F(n-6)

E(n) = 4*En+E(n-2),E1 = 2,E2 = 8

//3.F(n) = 4F(n-3)+F(n-6)
public static void solution3() {
    int limit = 4000000;
    int a = 2;
    int b = 8;
    int c = 4*a + b;
    int sum = 0;
    while (c < limit) {
        sum = sum +c ;
        a = b;
        b = c;

    }
    System.out.println(sum);
}

3.总结:

斐波那契数列求解:

1.递归求解

//    斐波那契数列:除了第一项和第二项,所有的数列的值都是前一项和前一项的前一项的加和 1,1,2,3,5,8,13
/*
     * 求第n项斐波那契数的值(递归法)
     * */
public static int f(int n) {
    if (n < 1) {
        return 0;
    } else if (n == 1 || n == 2) {
        return 1;
    } else {
        return f(n - 1) + f(n - 2);
    }
}

2.动态规划

对于 fib(10000),我们从 fib(1), fib(2), fib(3) ...... fib(10000),

从最小的值开始求解,并且把每次求解的值保存在“记忆”(可以是一个数组,下标正好用来对应 n 的解答值)里,下面的值都由记忆中的值直接得出。

这样等到算到 10000 的时候,我们想要求解的值自然也就得到了,直接从记忆[10000]里取到值返回即可。

那么这种接法其实只需要一个 for 循环,而不需要任何递归的逻辑。

/*
     * 求第n项斐波那契数的值(动态规划法法)
     * */
public static int f2(int n) {
    int[] dp = new int[n + 1];
    //初始条件
    dp[0] = 0;
    dp[1] = 1;

    //从2开始,不断地用之前记忆的值
    //填充数组
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];

}

动态规划:

recursion(递归) + memoization(记忆化)

递归有些值他会重复计算,会导致时间复杂度是指数级的。

记忆化就是在递归的基础上,我们记住或者缓存住一些数据

Problem 3Largest prime factor

1.题干

The prime factors of 13195 are 5, 7, 13 and 29.

What is the largest prime factor of the number 600851475143?

2.求解

首先给出百度百科中质因数的一些例子:

  • 1没有质因子。
  • 5只有1个质因子,5本身。(5是质数。)
  • 6的质因子是2和3。(6 = 2 × 3)
  • 2、4、8、16等只有1个质因子:2(2是质数,4 = 2,8 = 2,如此类推。)
  • 10有2个质因子:2和5。(10 = 2 × 5)

在我们理解了质因数的定义后求质因数的算法如下:
(1) i = 2,如果i < n,并且n能被K整除,则 n = n/k;
(2)如果n不能被i整除,则k = k+1,继续执行步骤1
(3)直到k = n,循环结束
我的实现Java代码:

因为一个数的任何质因数都小于这个数,如果我们从2开始,然后按比例放大它,我们可以假设第一个模数为0的数是这个数的质因数。

然后,我们可以用原始数除以它的第一个质因数,留下一个更小的数,然后检查它是否还能被相同的因数整除。当它停止时,我们增加数字,直到找到下一个因子,以此类推。

最后,退出条件是当我们将数字除以它本身时,这意味着它是最后一个质数因子。

通过这样做,我们只输出最后一个因子

public static void solution4() {
    Long num = 600851475143L;
    Long aux = 2L;
    while (num != 1) {
        if (num % aux == 0) {
            num /= aux;
        } else {
            aux += 1;
        }
    }
    System.out.println(aux);
}

3.总结:

1.输出Map的方法

HashMap hm = new HashMap();
		hm.put(1, "chen");
		hm.put(2, "chen");
		hm.put(3, "zhang");
		hm.put(4, "wang");
		hm.put(2, "sun");
//1、
System.out.println(hm);//默认调用toString
System.out.println("\n----------------------");
//2、   低效
Set keys = hm.keySet();
for (Object key : keys) {
    System.out.print(key+"="+hm.get(key));
}
System.out.println("\n----------------------");
//3\***   高效
Set<Map.Entry> ms =hm.entrySet();
for (Map.Entry entry : ms) {
    System.out.print(entry.getKey()+"="+entry.getValue());
}

2.质数和合数:

image-20211103092344599

一、性质不同
1、质数:是在大于1的自然数中,除了1和它本身以外不再有其他因数。
2、合数:是自然数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。
二、特点不同
1、质数:质数的个数是无穷的;在一个大于1的数a和它的2倍之间(即区间(a,
2a]中)必存在至少一个素数。
2、合数:所有大于2的偶数都是合数;所有大于5的奇数中,个位为5的都是合数;除0以外,所有个位为0的自然数都是合数;所有个位为4,6,8的自然数都是合数。

扩展资料:
质数的特点:
(1)质数p的约数只有两个:1和p。
(2)初等数学基本定理:任一大于1的自然数,要么本身是质数,要么可以分解为几个质数之积,且这种分解是唯一的。
(3)若n为大于或等于2的正整数,在n到n!之间至少有一个质数。

image-20211103092435011

3.判断素数:

//判断是否是素数
public static boolean isPrime(int n) {
    boolean flag = true;
    if (n < 2) {
        return false;
    }
    for (int i = 2; i < Math.sqrt(n); i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return flag;
}

Problem 4Largest palindrome product

1、题干

A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009=91×99.

Find the largest palindrome made from the product of two 3-digit numbers.

最大回文乘积

回文数就是从前往后读和从后往前读都一样的数。由两个2位数相乘得到的最大的回文数是 9009=91×99。

求由两个3位数相乘得到的最大的回文数。

2.求解

官方直接求解1:

//官方解答:
//1.求反
public static int reverse(int n){
    int reversed = 0;
    while (n>0){
        reversed = 10*reversed+n%10;
        //            System.out.println("reversed = "+reversed);
        n = n/10;
        //            System.out.println("n = "+n);
    }
    return reversed;
}
//2.判断是否是回文
public static  boolean isPalindrome(int n){
    if (n == reverse(n)) {
        return  true;
    }
    return false;
}
//3.求解
public static void solution2(){
    int largestPalindrome = 0;
    int product = 0;
    //遍历找出最大值
    for (int a = 100; a < 1000; a++ ){
        for (int b = 100; b < 1000; b++ ){
            product = a * b;
            if (product > largestPalindrome && isPalindrome(product)) {
                largestPalindrome = product;
            }
        }
    }
    System.out.println(largestPalindrome);
}

对于这个问题这已经够快了,但是还可以改进。这个方法会重复检查许多数字多次,比如说69696在 a = 132 或者 b= 528的时候或者当 a = 528或者 b = 132的时候已经被检查过了。为了防止这种重复检查,我们可以约定 a<=b;粗略的可以减少一半的计算。

这里Now b=a instead of 100

//3.求解
public static void solution2(){
    int largestPalindrome = 0;
    int product = 0;
    //遍历找出最大值
    for (int a = 100; a < 1000; a++ ){
        for (int b = a; b < 1000; b++ ){//Now b=a instead of 100
            product = a * b;
            if (product > largestPalindrome && isPalindrome(product)) {
                largestPalindrome = product;
            }
        }
    }
    System.out.println(largestPalindrome);
}

下一步我们应该考虑从999计数,而不是从100开始计数。

这样可以让程序更早的找到最大值。

如果乘积比我们现在能找到的最大回文数都小,我们可以跳过检查a和b是否乘积是回文数。

public static void solution3(){
    int max = 0;

    for (int a = 999;a>=100;a--){
        for (int b = 999; b >=a ; b--) {
            if (a*b<=max){
                break;//因为这时太小了不用判断了
            }
            if (isPalindrome(a*b)){
                max  = a*b;
            }

        }
    }
    System.out.println(max);
}

我们可以通过进一步分析进一步提升效率,我们可以把这个P 的每一位设为 x , y ,z;

P肯定是一个至少六位数长的数字。

P = XYZZYX

P = 100000*x+10000y+1000z+100z+10y+x

= 100001x + 10010y+ 1100z

= 11(9091 +910 +100)

因为 11 是一个指数,并且a或者b之间肯定有有一个11的质因子,所以如果我们知道a如果不能被11整除,那么b肯定能被11整除。使用这个信息我们可以按照b的值来检查啊

public static void solution4(){
    int max = 0;
    int b = 0;
    int db = 0;
    for (int a = 999; a >=100 ; a--) {
        if(a %11==0){
            b = 999;
            db= 1;//那么b就不能被11整除
        }else {
            b = 990;//最大的能整除11并且小于999的数字
            db = 11;//那么b能被11整除
        }
        for ( ; b >=a ; b = b-db) {
            if (a*b<=max){//数比我们已经找到的回文数还小不判断
                break;
            }
            if (isPalindrome(a*b)){
                max = a*b;
            }
        }
    }
    System.out.println(max);
}

这里特别巧妙的就是更改b的检测间隔,如果a能整除11,那么b就不能整除,那么b就老老实实的按照1的间隔去检验,但是如果a不能整除11,那么b肯定就能整除11,那么就按照11的间隔去变化,减少无效项数的检验,但是要注意这里b的初始值不同,因为如果要按照11的间隔来变化的话,我们需要从990开始变化,因为990是第一个小于999的最大的能被11整除的数字。


我的暴力求解:

//暴力直接求解
    public static void solution1() {
        int sum = 0;
//        1.初始化二维数组求最大值
        int[][] result = new int[1000][1000];
        for (int i = 0; i <= 999; i++) {
            for (int j = 0; j <= 999; j++) {
//                遍历所有乘积
                sum = i * j;
                String sumstr = Integer.toString(sum);
                char[] str = sumstr.toCharArray();
                //2.判断保存最大值
                if (str.length == 5) {
                    //判断对称最小100*100 = 10000五位数字
                    if (str[0] == str[4] && str[1] == str[3]) {
                        result[i][j] = sum;
                    }
                }
                if (str.length == 6) {
                    //判断对称最大999*999 = 998001六位数字
                    if (str[0] == str[5] && str[1] == str[4] && str[2] == str[3]) {
                        result[i][j] = sum;
                    }
                }
            }
        }
//        3.遍历数组,找到保存的最大值
        int max = 0;
        for (int i = 1; i <= 999; i++) {
            for (int j = 1; j <= 999; j++) {
                if (max < result[i][j]) {
                    max = result[i][j];
                    System.out.println(i);
                    System.out.println(j);
                    System.out.println(max);
                }
            }
        }
    }

3.总结:

1.求回文

    public static int reverse(int n){
        int reversed = 0;
        while (n>0){
            reversed = 10*reversed+n%10;
//            System.out.println("reversed = "+reversed);
            n = n/10;
//            System.out.println("n = "+n);
        }
        return reversed;
    }

2.判断回文,因为这个题肯定是6位数字,所以直接判断求回文之前和之后的值就可以了

如果用这种方法,是奇数个数字需要reversed最后会比x高一位,只需要reversed / 10 == x即可。 reversed / 10 == x。

比如x = 101,reversed = 0*10+101%10 = 1

x = 10,reversed = 1*10 + 10%10 = 0;

x = 0;

reversed= 10;

原来的数字x = 101/10 = 10 判断与reverse的是否相等。

/*
    * 接着如何通过翻转后的数字来判断回文呢?
    * 对于偶数位原数字,判断reversed == x即可。
    * 对于奇数数字,reversed最后会比x高一位,只需要reversed / 10 == x即可。
    * 因为中间的一位数字只有一个,不会影响回文序列。
    * */
public static boolean isReverse(int x){
    int reversed = 0;
    while (reversed < x) {
        //            具体做法是,不断地对原数字x进行取余,得到的结果保存在变量reversed中。
        //            当在reversed < x时,继续循环,reversed * 10作为高一位,同时继续对x取余,加上余数作为低位。
        //            同时对x / 10。
        reversed = reversed * 10 + x % 10;
        System.out.println("reversed"+reversed);

        x /= 10;
        System.out.println("x"+x);
    }
    return reversed == x || reversed / 10 == x;
}

分析:

法1:如果将整数转换为字符串来解决这个问题,会非常简单,只需要对转换后的字符串进行一次遍历就可以了。而且只需要遍历一半的元素。

法2:如果根据进阶的要求。我们可以对数字进行一半的翻转。我们求取数字的后一半的翻转。具体做法是,不断地对原数字x进行取余,得到的结果保存在变量reversed中。当在reversed < x时,继续循环,reversed * 10作为高一位,同时继续对x取余,加上余数作为低位。同时对x / 10。

例如:对121进行上述操作,

  1. reversed = 1

    x = 12

  2. reversed = 1 * 10 + 2

    x = 1

注意事项:

有一些数我们是可以直接判断不能构成回文序列的。

  • 负数,由于存在负号。
  • 个位为0,且不为0的数。高位无法存在0。

Problem 5Smallest multiple

1.题干

2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.

What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20?


最小公倍数

2520是最小的能够被1到10整除的数。

最小的能够被1到20整除的正数是多少?

2.求解

1.不需要任何编程就可以解决,直接从1×2...×19×20

2.暴力求解:

public static void solution1() {
    Long num = 1L;
    while (!((num % 1 == 0) && (num % 2 == 0) && (num % 3 == 0) & (num % 4 == 0) && (num % 5 == 0) && (num % 6 == 0) && (num % 7 == 0) && (num % 8 == 0) && (num % 9 == 0) && (num % 10 == 0) && (num % 11 == 0) && (num % 12 == 0) && (num % 13 == 0) && (num % 14 == 0) && (num % 15 == 0) && (num % 16 == 0) && (num % 17 == 0) && (num % 18 == 0) && (num % 19 == 0) && (num % 20 == 0))) {
        num = num + 1;
    }
    System.out.println(num);
}

因为N是能被从2到k的所有整数整除的最小数,具有这个性质的最小值N,我们必须保证在质因数分解时只包含绝对必要的素因子。

比如k的三种情况:

k = 2; N = 2

k =3 ;N = 6

k = 4;N = 2*3*2 = 12

我们可以看到,当k = 4时,我们不需要计算2*3*4,因为有一个2在里面4 = 2*2的质因数分解已经包括在内。

如果我们现在考虑下一个
两种情况:
k = 5, N = 2*2*3*5 = 60
k = 6, N = 2*2*3*5 = 60
我们可以看到,对于k = 5和k = 6, N = 60,因为如果N包含因子2,3它已经包含了能被6整除的所有必要条件。
将这一原则应用于k = 20的情况:
N = 2 * 3 * 2 * 5 * 7 * 2 * 3 * 11 * 13 * 2 * 17 * 19 = 232792560

能被1至n整除的最小数:

1.首先筛选出[1,n]的所有素数,记为primeList[i]。
2.根据算术基本定理:“任何一个合数都可以分解为几个素数的积”,求出每一个Pn的kn值,如下所示:

  • 假设Pn表示第n个素数,那么任意正整数可以通过下面的式子获得:

    img

  • Kn 的取值:要保证最终值可以被所有含Pn约数的数整除(如下面例子,要保证Num既能被2整除,也能被4,8整除),因此公式为:primeList[i]^k<=n

  • 例如:一个整数要能被1-10的所有整数整除,那么就等同于他能被1-10之间的所有素数整除。那么此时:

    img

    3.求出各素数次幂的积,即求出Num。

Problem 6Sum square difference

The sum of the squares of the first ten natural numbers is,

12+22+…+102=385

The square of the sum of the first ten natural numbers is,

(1+2+…+10)2=552=3025

Hence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is 3025−385=2640.

Find the difference between the sum of the squares of the first one hundred natural numbers and the square of the sum.

平方的和与和的平方之差

前十个自然数的平方的和是

12+22+…+102=385

前十个自然数的和的平方是

(1+2+…+10)2=552=3025

因此,前十个数的平方的和与和的平方之差是3025−385=2640。

求前一百个数的平方的和与和的平方之差。

2.求解

1.直接暴力求解:

因为100这个限制不算一个很高的上限。我们可以这样

我们都知道的我们可以求数的和用这个公式

image-20211101112013987
//法一:暴力破解
public static  void solution1(){
    int n1= (100+1)*100/2;
    int sum1 = n1*n1;
    int sum2 = 0;
    for (int i = 1; i <= 100; i++) {
        sum2 = i*i+sum2;
        System.out.println(sum2);
    }
    System.out.println(sum1-sum2);
}

2用平方的和:

找一个函数f(n),它对任意n都是12到n2的和

假设它是f(n) = an3 + bn2 + cn + d的形式,我们需要确定a b c d的常数。

因为我们可以证明f(0) = 0 f(1) = 1 f(2) = 5 f(3) = 14。这样就得到了四个方程

变量,即

image-20211105081607403

解这个方程组,我们得到a =1/3,b = 1/2;c = 1/6; d= 0;这样就能求出来

f(n) = 1/6(2*n3+3*n2+n) = n/6*(2n+1)(n+1)

为了检验这个公式,我们算一下

f(n+1) = (n+1)/6*(2n+3)(n+2) = 1/3*n3+3/2*n2+13/6*n+1

f(n+1) = f(n) + (n+1)n

我们可以证明f是一个正确的公式,我们可以简化求平方的和

//法二:用两个公式
public static  void solution2(){
    int limit = 100;
    int sum = limit*(limit+1)/2;
    int sum2 = (2*limit+1)*(limit+1)*limit/6;

    System.out.println(sum*sum-sum2);
}

3.总结

1.整数除法的漏洞

特别注意这里做除法的时候要把除数放在最后试试

int sum2 = limit/6*(2*limit+1)*(limit+1);

正确:

想保留小数,先定义一个双精度浮点型的变量double num;然后用num接收结果,需要相除的时候进行一下强转,因为这两数本来是整型数,要事先把他们变成浮点型再相除答案才正确.最后输出.


double num=0.0;
num=(double) = limit/6*(2*limit+1)*(limit+1);;
Syste,.out.println("两数相除的结果是:"+num);

Problem 710001st prime

一、题干

By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13.

What is the 10 001st prime number?

第10001个质数

前6个质数分别是2、3、5、7、11和13。

第10 001个质数是多少?

二、解答

posted @ 2021-11-02 09:18  记录学习Blog  阅读(226)  评论(0编辑  收藏  举报