LeetCode : 866. 回文素数(Prime Palindrome)分析和解答

866. 回文素数

求出大于或等于 N 的最小回文素数。

回顾一下,如果一个数大于 1,且其因数只有 1 和它自身,那么这个数是素数。

例如,2,3,5,7,11 以及 13 是素数。

回顾一下,如果一个数从左往右读与从右往左读是一样的,那么这个数是回文数。

例如,12321 是回文数。

示例 1:

输入: 6

输出: 7

示例 2:

输入: 8

输出: 11

示例 3:

输入: 13

输出: 101

提示:

  • 1 <= N <= 10^8
  • 答案肯定存在,且小于 2 * 10^8

解答

思路:

判断素数的方法和判断回文数的方法都不难,难点在于怎么在短时间内完成,即不能超时。

起先我只是想办法优化寻找素数的方法,然后再去判断是否是回文数。然后发现一直会超时,原因是数字很大后,这样执行的效率很低。这题主要的优化策略在判断回文数上了,而素数的优化起到的是辅助性的增强。

数学规律1:除 11 外的偶数位回文数,都能被 11 整除

能被 11 整数的数字有个特点:它们的偶数位的和,等于他们奇数位的和。这是一条已经被证明的定理了,想要详细了解的话可以另行百度。

而偶数位的回文数,由于回文数的特点,以及它又是偶数位,所以它偶数位的和一定等于奇数位的和,即它能被 11 整除,所以除了 11 其余的都不是素数。

结论: 我们可以跳过所有位数为偶数的数字,除了 11。例如当输入 123456 时,我们可以直接从 1000001 开始查找。

数学规律2:除 2 和 3 外,所有的素数一定在 6 的两侧

什么意思呢,也就是说除了 2 和 3,其余素数一定等于 6x-16x +1,其中 x >= 1。

首先 6x 肯定不是质数,因为它能被 6 整除;其次 6x+2 肯定也不是质数,因为它还能被 2 整除;依次类推,6x+3 肯定能被 3 整除;6x+4 肯定能被 2 整除;6x+5 就等同于 6x-1

结论: 我们可以将遍历数字时的步长再增大点,而不是只过滤偶数。其次,在判断数字是否为素数时,我们也只需要判断该数字能否被 6 两侧的数字整除即可。(不少解法只过滤偶数,这样以 6 个数为单位,需要判断剩下的 3 个,而该方法只需要判断剩下的 2 个,效率提升 30% 左右)

注意: 你也可以选择只过滤偶数,同样可以在指定时间内完成。虽然会慢些,但是代码逻辑会简单不少。

其余优化

  1. 先使用上述两条数学规律过滤掉大量情况,这样我们需要实际计算的数字就少很多很多了。
  2. 其次对于满足上述两种条件的数字,我们先判断它是否是回文数,因为判断是回文数的效率会比判断是素数的效率高很多。
  3. 由于 11 是个特别的情况(同理还有 2 和 3),我不想在每次位数为偶数时还去判断它是不是 11,所以我选择直接把 11 以下的数单独处理了,它们也不多。

实现

代码中又多加了一些注释,应该不难理解了。

/**
 * Copyright © 2018 by afei. All rights reserved.
 * 
 * @author: afei
 * @date: 2018年11月3日
 */

class Solution {

    public static void main(String[] args) {
        // 这个例子很容易超时,要保证在一秒内完成,本例方法只需 10ms
        System.out.println(primePalindrome(9989900));
    }

    public static int primePalindrome(int N) {
        if (N <= 11) { // 单独处理 11 以下的数,包括 11
            if (N <= 2)
                return 2;
            else if (N == 3)
                return 3;
            else if (N <= 5)
                return 5;
            else if (N <= 7)
                return 7;
            else
                return 11;
        }
        // when N > 11
        int modulus = N % 6; // 这个用来挑选余数为 1 或 5 的数字
        switch (modulus) {
        case 0:
            N++;
        case 1:
            modulus = 1;
            break;
        case 2:
            N++;
        case 3:
            N++;
        case 4:
            N++;
        case 5:
            modulus = 5;
            break;
        }
        int[] nums = new int[10]; // 这个用来判断回文数的,长度 10 就够用了
        for (;;) {
            int length = getNumLength(N, nums);
            if ((length & 1) == 0) {
                // if length is even, palindrome must be divided by 11
                N = (int) Math.pow(10, length) + 1;
                modulus = 5; // modulus must be 5
                continue; // 跳过所有位数为偶数的数字
            }
            // here modulus must be 1 or 5
            if (isPalindrome(length, nums) && isPrime(N)) {
                return N;
            }
            // 步长为 2 或者 4,效率更高
            if (modulus == 1) {
                N += 4;
                modulus = 5;
            } else { // modulus == 5
                N += 2;
                modulus = 1;
            }
        }
    }

    public static int getNumLength(int N, int[] nums) {
        int length = 0;
        while (N > 0) {
            nums[length++] = N % 10;
            N /= 10;
        }
        return length;
    }

    public static boolean isPrime(int num) {
        int sqrt = (int) Math.sqrt(num);
        // We have filtered nums what "N % 6 != 1 || N % 6 != 5"
        for (int i = 5; i <= sqrt; i += 6) {
            // So here just check "i % 6 == 1 || i % 6 == 5" 
            if (num % i == 0 || num % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }

    public static boolean isPalindrome(int length, int[] nums) {
        for (int i = 0; i < length / 2; i++) {
            if (nums[i] != nums[length - i - 1]) {
                return false;
            }
        }
        return true;
    }
}

项目地址

https://github.com/afei-cn/LeetCode/tree/master/866.%20Prime%20Palindrome

原题地址

https://leetcode-cn.com/problems/prime-palindrome/description/

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
给定一个整数数组 nums 和一个目标值 target,要求在数组中找出两个数的和等于目标值,并返回这两个数的索引。 思路1:暴力法 最简单的思路是使用两层循环遍历数组的所有组合,判断两个数的和是否等于目标值。如果等于目标值,则返回这两个数的索引。 此方法的时间复杂度为O(n^2),空间复杂度为O(1)。 思路2:哈希表 为了优化时间复杂度,可以使用哈希表来存储数组中的元素和对应的索引。遍历数组,对于每个元素nums[i],我们可以通过计算target - nums[i]的值,查找哈希表中是否存在这个差值。 如果存在,则说明找到了两个数的和等于目标值,返回它们的索引。如果不存在,将当前元素nums[i]和它的索引存入哈希表中。 此方法的时间复杂度为O(n),空间复杂度为O(n)。 思路3:双指针 如果数组已经排序,可以使用双指针的方法来求解。假设数组从小到大排序,定义左指针left指向数组的第一个元素,右指针right指向数组的最后一个元素。 如果当前两个指针指向的数的和等于目标值,则返回它们的索引。如果和小于目标值,则将左指针右移一位,使得和增大;如果和大于目标值,则将右指针左移一位,使得和减小。 继续移动指针,直到找到两个数的和等于目标值或者左指针超过了右指针。 此方法的时间复杂度为O(nlogn),空间复杂度为O(1)。 以上三种方法都可以解决问题,选择合适的方法取决于具体的应用场景和要求。如果数组规模较小并且不需要考虑额外的空间使用,则暴力法是最简单的方法。如果数组较大或者需要优化时间复杂度,则哈希表或双指针方法更合适。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值