• 求对称质数-一道算法题的思考优化过程


      在孤尽大佬的开课吧公开课视频(地址: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));
        }
    }

  • 相关阅读:
    02-30 线性可分支持向量机
    02-28 scikit-learn库之线朴素贝叶斯
    02-27 朴素贝叶斯
    02-26 决策树(鸢尾花分类)
    047 选项模式
    第二节:师傅延伸的一些方法(复习_总结)
    第一节:登录流程
    第一节:对应拼音编码查询(后续更新)
    前端对象
    Form表单
  • 原文地址:https://www.cnblogs.com/bruceChan0018/p/15288333.html
Copyright © 2020-2023  润新知