某司OA2 Split String into Primes

本文探讨了一种将给定字符串分割成质数数字段的方法,介绍了递归、动态规划和Eratosthenes筛选法的应用,旨在解决如何计算所有可能的分割组合。作者详细解释了质数判断、递归实现、记忆化搜索和动态规划的思路,以及如何优化时间复杂度和空间使用。
摘要由CSDN通过智能技术生成

题目见https://www.geeksforgeeks.org/count-of-ways-to-split-a-given-number-into-prime-segments/。大概就是把一个全是数字的string任意分割成数字,并要求这些被分出来的数字都是质数,求一共有多少种组合。

作为一个不懂递归不懂dp的,做这题真是太难太痛苦了QAQ

首先先把判断质数的方法单独拎出来说,因为这道题说了分出来的数字比10^6小,所以我们其实可以先用Eratosthenes筛子法把所有1 - 10^6每个数字是否为奇数给cache下来形成一个boolean数组,这样判断分出来的数字是否是prime的时候就很方便的O(1)了。

 private static int HIGH = 1000000;
    private static int MOD = 1000000007;
    private static boolean[] seive = new boolean[HIGH];

    public static void buildSeive() {
        Arrays.fill(seive, true);
        seive[0] = false;
        seive[1] = false;
        for (int i = 2; i < HIGH; i++) {
            if (seive[i]) {
                for (int j = 2; i * j < HIGH; j++) {
                    seive[i * j] = false;
                }
            }
        }
    }

    public static boolean isPrime(int num) {
        return seive[num];
    }

接下来就是重头戏之递归和dp。

首先是gfg上提供的递归解法。假设string的长度为n,递归的主要思想就是我们可以把这个string分成两部分,[0, i - 1]和[i, n -1],如果[i, n-1]是个prime的话我们就递归调用这个函数,看看[0, i - 1]可以被分成多少,最后一直递归到[0, 0]的时候base case直接return 1,表示现在这是个有效的解(这里没有完全搞透)。

那么如何把这个思想转换成代码,首先要写个recursion函数,根据上面的思想,函数参数需要传入string和当前要判断的部分的last index。写完base case以后就要开始划重点了。首先声明一个count变量来记录个数。split的时候因为可以在任意位置split(但是因为题目给了数字最多10^6,所以相当于至少都要在倒数第6位之后split),所以我们需要写一个for循环从1到6,在这个for循环中看看从string末尾到当前遍历到的这个位置(也就是上文说的[i, n - 1])是否是prime(当然还要做好boundary check,以及注意不能以0开头)。如果是prime的话,count就要加上对前半部分递归调用这个函数产生的结果,因为题目还要求mod了所以就再mod一下就好。时间复杂度O(n^2),空间O(n)。(虽然没想明白orz)

public static int stringPrime0(String inputString) {
        buildSeive();
        return (naiveRecursion(inputString, inputString.length()));
    }

    public static int naiveRecursion(String inputString, int index) {
        if (index == 0) {
            return 1;
        }
        int count = 0;
        for (int j = 1; j <= 6; j++) {
            if (index - j >= 0 && inputString.charAt(index - j) != '0' &&
            isPrime(Integer.parseInt(inputString.substring(index - j, index)))) {
                count += naiveRecursion(inputString, index - j);
                count %= MOD;
            }
        }
        return count;
    }

然后由于递归会出现重复计算,于是就可以memoization一下,也不是什么高大上的东西,就是多pass个数组来存当前结果,如果已经计算过的话就直接从数组里return就好……只是需要注意,数组最开始初始化的时候要初始化成-1,以免跟真正的结果撞了导致直接return,以及开的数组大小要是len + 1,因为最开始调用的时候传入的是len。

public static int stringPrime1(String inputString) {
        buildSeive();
        int len = inputString.length();
        int[] memo = new int[len + 1];
        Arrays.fill(memo, -1);
        memo[0] = 1;
        return memoizationRecursion(inputString, len, memo);
    }

    public static int memoizationRecursion(String inputString, int index, int[] memo) {
        if (memo[index] != -1) {
            return memo[index];
        }
        int count = 0;
        for (int j = 1; j <= 6; j++) {
            if (index - j >= 0 && inputString.charAt(index - j) != '0' &&
            isPrime(Integer.parseInt(inputString.substring(index - j, index)))) {
                count += memoizationRecursion(inputString, index - j, memo);
                count %= MOD;
            }
        }
        memo[index] = count;
        return count;
    }

然后就是dp了,dp研究了老半天才想明白,借鉴了https://stackoverflow.com/questions/59407508/how-to-count-the-number-of-ways-a-given-string-can-be-split-into-prime-numbers,但刚开始从py翻译成java总搞不对。跟递归很像但是又有点不一样。前面的递归是从后往前做的,dp因为要依据前面的计算结果所以感觉从前往后做比较顺一些。dp的整体思路在于,首先我们要外层循环i把整个string遍历一遍(类似于递归里面的index参数),内层循环j把1-6遍历一遍,用dp[i]表示以i结尾的字符串有多少种分法。和递归类似,如果[i - j, i]是个prime,那么dp[i]就要加上前半部分的分法数量,即dp[i - j]。表达式大概是dp[i] = sum(dp[i - j]) for (i - 6 <= j < i) if dp[i - j] > 0 && isPrime(input[i - j, i])。写成代码的话需要把dp[0]初始化成1,代表[0, i]这整个string都是个prime:

public static int stringPrime(String inputString) {
        buildSeive();
        int len = inputString.length();
        int[] dp = new int[len + 1];
        dp[0] = 1;
        for (int i = 1; i <= len; i++) {
            int count = 0;
            for (int j = 1; j <= 6; j++) {
                if (i - j >= 0 && inputString.charAt(i - j) != '0' &&
                        isPrime(Integer.parseInt(inputString.substring(i - j, i)))) {
                        count += dp[i - j];
                }
            }
            dp[i] = count;
        }
        return dp[len];
    }

刚刚终于搞明白stackoverflow上的那个解法,和我自己的想法有一点点不一样,就是在已经知道当前[i - j, i]是个prime的时候,直接加上dp[i - j],然后最后再加上判断当前[0, i]这整个string是否是prime,如果是的话就dp[i]++,好像也就相当于把我的dp[0] = 1的部分变成了后面这个判断?嗯是的,又想了想感觉好像还是这个方法更直观一点,不需要前面特意搞dp[0],哎,还是我太菜了。

public static int stringPrimeFromStackOverflow(String inputString) {
        buildSeive();
        int len = inputString.length();
        int[] dp = new int[len + 1];
        for (int i = 1; i <= len; i++) {
            int count = 0;
            for (int j = 1; j <= 6; j++) {
                if (i - j >= 0 && inputString.charAt(i - j) != '0' &&
                        isPrime(Integer.parseInt(inputString.substring(i - j, i)))) {
                    count += dp[i - j];
                }
            }
            dp[i] = count;
            if (isPrime(Integer.parseInt(inputString.substring(0, i)))) {
                dp[i]++;
            }
        }
        return dp[len];

 

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可 6私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值