在孤尽大佬的开课吧公开课视频(地址:https://www.kaikeba.com/open/item?c=681&channelCode=gjsh6ytvxy)中,有一道很有意思的题目:

  首先,我们需要写出如何判断对称数和质数两个函数。
  对于质数的判断,我们不难想到,如果可以整除,那么能被整除的因子至少有一个会小于等于这个数字的开平方。比如25,至少要有一个因子小于等于5。100,至少要有一个因子要与等于10。所以一个简单的思路就是:
  1.判断是否为奇数(偶数肯定不是质数);
  2.将该数开平方,得到的数字加1,以这个结果为最大值,如果从3开始,每隔2个数字一直到这个最大值进行除余,如果有任意数字除余结果为0,那么该数不为质数,如果全部不为0,那么该数为质数(此处使用了6n+-1法则,后续有说明);
最终代码如下:

    private static boolean isPrime(int inputMaxNumber) {
        if (inputMaxNumber <= 1) {
            throw new RuntimeException("out of range");
        }
        if (inputMaxNumber == 2 || inputMaxNumber == 3) {
            return false;
        }
        if (inputMaxNumber % 6 != 1 && inputMaxNumber % 6 != 5) {
            return false;
        }
        int sqrt = (int) Math.sqrt(inputMaxNumber);
        for (int i = 5; i <= sqrt; i += 6) {
            if (inputMaxNumber % i == 0 || inputMaxNumber % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }

  对于对称数的判断,最简单的能想到的就是拆分为个位数字,然后头尾比较。但是这样的话,当数据量增大时,会出现大量的字符串对象,这种做法并不可取。这里用了一个办法,构造一个新数字,根据输入数字循环取余后除10,与此同时新数乘10并加上余数,最终如果新数与输入数字相等,那么判定为对称数。

  最终代码如下:

    private static boolean isBalanced(int source) {
        if (source <= 9) {
            throw new RuntimeException("out of range");
        }
        int original = source;
        //构造新数字,以原数字的个位为最高位,十位为次,...依次到原数字的最高位成为本数字的个位
        int buildNew = 0;
        while (source != 0) {
            buildNew = buildNew * 10 + source % 10;
            source /= 10;
        }
        if (buildNew == original) {
            return true;
        }
        return false;
    }

---------------------------------------------------------------

  这道题,最不难想到的就是循环一个一个去找这些质数且对称的数字。但是考虑到性能,数据量稍微一大,那么耗时肯定会大大增加。怎么去优化这个过程呢?

  视频中提到了几个优化点:
  1.先判断是否为对称数,再判断是否为质数。因为对称数的判断比判断质数要简单得多,所以我们优先判断对称数,从消耗上会比小得多;
  2.步长为1的循环优化的空间很大,这里的优化使用到了6N+-1法
  6N+-1法则:任何一个自然数,总可以表示成如下形式之一:6N,6N+1,6N+2,6N+3,6N+4,6N+5 (N=0,1,2,3,..),显然,当N≥1时,6N,6N+2,6N+3,6N+4都不是素数,只有形如6N+1和6N+5的自然数才可能是素数,所以除了2,3外,所有的素数都可以表示成6N±1的形式(N=0,1,2,3,..)

  到了这里,程序的性能已经比之前提升了很多。

  执行代码寻找10亿的结果并计时:

---------------------------------------------------------------
  仔细想想,这个题目还是很有意思,继续思考,其实还会发现有进一步的优化空间。
  这里找的既是对称数,又是质数,所以两位以上的数字,凡以2,4,5,6,8开头的数字都可以略过。如此一来,当数据量很大时,我们可以跳过很多不符合要求的数字段。比如寻找10亿以内符合要求的数字时,我们可以确定2000-3000,20000-30000,2亿-3亿...都不符合要求,所以可以直接跳过,这就节省了很多比较操作了。

  这里给出代码,并与上面的执行结果进行比较:

public class BalancedPrime {
    public static final List<Integer> INTEGER_LIST = new ArrayList<>();

    static {
        INTEGER_LIST.add(2);
        INTEGER_LIST.add(3);
        INTEGER_LIST.add(5);
        INTEGER_LIST.add(7);
    }

    private static void processNumberLtTen(int source) {
        ArrayList<Integer> integerArrayList = new ArrayList<>();
        integerArrayList.add(2);
        if (source >= 3) {
            integerArrayList.add(3);
        }
        if (source >= 5) {
            integerArrayList.add(5);
        }
        if (source >= 7) {
            integerArrayList.add(7);
        }
        System.out.println("integerArrayList = " + integerArrayList.toString());
    }


    public static void find(int source) {
        if (source <= 1) {
            throw new RuntimeException("out of range");
        }
        if (source < 10) {
            processNumberLtTen(source);
            return;
        }

        //遇关键数字跳跃数字段,可提升接近50%性能
        int ex = 0;
        int skipCount = 0;
        int limitSkipCount = -1;
        for (int i = 11; i <= source; i += 2) {
            if (skipCount == limitSkipCount || limitSkipCount == -1) {
                int current = i;
                while (current >= 10) {
                    current /= 10;
                    ex++;
                }
                limitSkipCount = ((int) Math.pow(10, ex)) / 2;
                if (current % 2 == 0 || current == 5) {
                    i = calculateCurrent(current, ex);
                } else {
                    limitSkipCount = (((current + 1) * (int) Math.pow(10, ex)) + 1 - i) / 2;
                }
                ex = 0;
                skipCount = 0;
            }
            skipCount++;

            compareAndProcess(i);
        }
        System.out.println("INTEGER_LIST = " + INTEGER_LIST.toString());
    }

    private static int calculateCurrent(int current, int ex) {
        if (current == 2) {
            return (3 * (int) Math.pow(10, ex)) + 1;
        } else if (current == 4 || current == 5 || current == 6) {
            return (7 * (int) Math.pow(10, ex)) + 1;
        } else if (current == 8) {
            return (9 * (int) Math.pow(10, ex)) + 1;
        } else {
            throw new RuntimeException("out of range");
        }
    }


    private static void compareAndProcess(int compare) {
        if (isBalanced(compare) && isPrime(compare)) {
            INTEGER_LIST.add(compare);
        }
    }

    private static boolean isBalanced(int source) {
        if (source <= 9) {
            throw new RuntimeException("out of range");
        }
        int original = source;
        //构造新数字,以原数字的个位为最高位,十位为次,...依次到原数字的最高位成为本数字的个位
        int buildNew = 0;
        while (source != 0) {
            buildNew = buildNew * 10 + source % 10;
            source /= 10;
        }
        if (buildNew == original) {
            return true;
        }
        return false;
    }

    private static boolean isPrime(int inputMaxNumber) {
        if (inputMaxNumber <= 1) {
            throw new RuntimeException("out of range");
        }
        if (inputMaxNumber == 2 || inputMaxNumber == 3) {
            return false;
        }
        if (inputMaxNumber % 6 != 1 && inputMaxNumber % 6 != 5) {
            return false;
        }
        int sqrt = (int) Math.sqrt(inputMaxNumber);
        for (int i = 5; i <= sqrt; i += 6) {
            if (inputMaxNumber % i == 0 || inputMaxNumber % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        long l = System.currentTimeMillis();
        find(1000000000);
        System.out.println("time escaped = " + (System.currentTimeMillis() - l));
    }
}

  执行结果:

  另一个思路,我们不用循环比较定位符合要求的数字,而是直接通过构造出对称的数字,同时判断这个数字是否为质数。这样一来,比较的范围就大大减少,如果叠加之前想到的2,4,5,6,8的排除,那么程序的性能相比会更加优秀。

  最终,将几个上述的关键点整合:

  • 1.构造对称数
  • 2.循环数字时,排除2,4,6,8,5开头的数字
  • 3.判断素数时,开方后采用6N+-1法则

  我们可以看看最终的结果:

  最后附上代码:

package com.changjiang.interesting;

import java.util.ArrayList;
import java.util.List;

/**
 * @author changjiang.chen
 * @description 找出N内的所有质数且对称的数字,包含个位数。注意:0和1既不是质数也不是合数。
 * @timestamp 2021-09-15 11:04:19
 * <p>
 */
public class BalancedPrime {
    public static final List<Integer> INTEGER_LIST = new ArrayList<>();

    static {
        INTEGER_LIST.add(2);
        INTEGER_LIST.add(3);
        INTEGER_LIST.add(5);
        INTEGER_LIST.add(7);
    }

    private static void processNumberLtTen(int source) {
        ArrayList<Integer> integerArrayList = new ArrayList<>();
        integerArrayList.add(2);
        if (source >= 3) {
            integerArrayList.add(3);
        }
        if (source >= 5) {
            integerArrayList.add(5);
        }
        if (source >= 7) {
            integerArrayList.add(7);
        }
        System.out.println("integerArrayList = " + integerArrayList.toString());
    }

    public static void find(int source) {
        if (source <= 1) {
            throw new RuntimeException("out of range");
        }
        if (source < 10) {
            processNumberLtTen(source);
            return;
        }

        //遇关键数字跳跃数字段,可提升接近50%性能
        int ex = 0;
        int skipCount = 0;
        int limitSkipCount = -1;
        for (int i = 11; i <= source; i += 2) {
            if (skipCount == limitSkipCount || limitSkipCount == -1) {
                int current = i;
                while (current >= 10) {
                    current /= 10;
                    ex++;
                }
                limitSkipCount = ((int) Math.pow(10, ex)) / 2;
                if (current % 2 == 0 || current == 5) {
                    i = calculateCurrent(current, ex);
                } else {
                    limitSkipCount = (((current + 1) * (int) Math.pow(10, ex)) + 1 - i) / 2;
                }
                ex = 0;
                skipCount = 0;
            }
            skipCount++;

            compareAndProcess(i);
        }
        System.out.println("INTEGER_LIST = " + INTEGER_LIST.toString());
    }

    private static int calculateCurrent(int current, int ex) {
        if (current == 2) {
            return (3 * (int) Math.pow(10, ex)) + 1;
        } else if (current == 4 || current == 5 || current == 6) {
            return (7 * (int) Math.pow(10, ex)) + 1;
        } else if (current == 8) {
            return (9 * (int) Math.pow(10, ex)) + 1;
        } else {
            throw new RuntimeException("out of range");
        }
    }

    private static void compareAndProcess(int compare) {
        if (isBalanced(compare) && isPrime(compare)) {
            INTEGER_LIST.add(compare);
        }
    }

    private static boolean isBalanced(int source) {
        if (source <= 9) {
            throw new RuntimeException("out of range" + source);
        }
        int original = source;
        //构造新数字,以原数字的个位为最高位,十位为次,...依次到原数字的最高位成为本数字的个位
        int buildNew = 0;
        while (source != 0) {
            buildNew = buildNew * 10 + source % 10;
            source /= 10;
        }
        if (buildNew == original) {
            return true;
        }
        return false;
    }

    private static boolean isPrime(int inputMaxNumber) {
        if (inputMaxNumber <= 1) {
            throw new RuntimeException("out of range");
        }
        if (inputMaxNumber == 2 || inputMaxNumber == 3) {
            return false;
        }
        if (inputMaxNumber % 6 != 1 && inputMaxNumber % 6 != 5) {
            return false;
        }
        int sqrt = (int) Math.sqrt(inputMaxNumber);
        for (int i = 5; i <= sqrt; i += 6) {
            if (inputMaxNumber % i == 0 || inputMaxNumber % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }

    private static int buildBalanceByPrefix(int number, boolean isEven) {
        int process = number;
        if (!isEven) {
            process /= 10;
        }
        int inverse = 0;
        int ex = 0;
        while (process > 0) {
            int i = process % 10;
            inverse = inverse * 10 + i;
            ex++;
            process /= 10;
        }
        return (number * (int) Math.pow(10, ex) + inverse);
    }

    /**
     * 构建与输入数字长度相同的对称数
     *
     * @param inputMaxNumber
     * @return
     */
    private static List<Integer> buildSameLengthBalance(int inputMaxNumber) {
        if (inputMaxNumber < 10) {
            throw new RuntimeException("out of range" + inputMaxNumber);
        }
        int process = inputMaxNumber;
        //数字长度
        int ex = 0;
        while (process > 0) {
            process /= 10;
            ex++;
        }

        //数字长度是否为偶数
        boolean isEven = ex % 2 == 0;
        int prefixCount = ex / 2;
        if (!isEven) {
            prefixCount += 1;
        }

        List<Integer> integerList = new ArrayList<>();
        boolean firstRounded = true;
        List<Integer> lastRounded = new ArrayList<>();
        for (int i = 0; i < prefixCount; i++) {
            integerList.clear();
            for (int j = 0; j < 10; j++) {
                if (firstRounded) {
                    if (j % 2 == 0 || j == 5) {
                        continue;
                    }
                    integerList.add(j);
                } else {
                    for (int lastNumber : lastRounded) {
                        integerList.add(lastNumber + j);
                    }
                }
            }
            if (firstRounded) {
                firstRounded = false;
            } else {
                lastRounded.clear();
            }
            for (int number : integerList) {
                lastRounded.add(number * 10);
            }
        }

        List<Integer> result = new ArrayList<Integer>();
        for (int k : integerList) {
            //避免溢出
            if ((isEven && k <= 21474) || (!isEven && ex / 2 <= 4)) {
                result.add(buildBalanceByPrefix(k, isEven));
            }
        }

        return result;
    }

    /**
     * 1.如果大于10,那么结果集包含2,3,5,7
     * 2.对于大于10的数字(两位以上的数字,含两位)的处理:
     * a.分析数字的位数,并判断这个位数的奇偶性。--构建所有可能的对称数
     * a1.打头的数字不能为0,2,4,5,6,8
     * a2.如果输入的是n位数,那么构建的n位数第一个数字不能比输入数字大
     *
     * @param inputMaxNumber
     */
    public static void findFromSymmetry(int inputMaxNumber) {
        if (inputMaxNumber <= 1) {
            throw new RuntimeException("out of range");
        }
        if (inputMaxNumber < 10) {
            processNumberLtTen(inputMaxNumber);
            return;
        }

        int process = inputMaxNumber;
        while (process >= 10) {
            List<Integer> integers = buildSameLengthBalance(process);
            for (Integer integer : integers) {
                //构建的比原输入数字大时,跳过
                if (integer > inputMaxNumber) {
                    continue;
                }

                //首位数字为偶数或者5,则不进行比较了
                int current = integer;
                while (current >= 10) {
                    current /= 10;
                }
                if (current % 2 == 0 || current == 5) {
                    continue;
                }

                //比较,并决定是否放入结果集
                compareAndProcess(integer);
            }
            //减少一位,继续循环,直到降低到个位数字
            process /= 10;
        }
    }

    public static void main(String[] args) {
        long l = System.currentTimeMillis();
        findFromSymmetry(1000000000);
        System.out.println("time escaped = " + (System.currentTimeMillis() - l));
    }
}

 

 

 

posted on 2021-09-15 20:49  长江同学  阅读(178)  评论(0编辑  收藏  举报