求出大于或等于N的最小回文素数。
素数:如果一个数大于1,且其因数只有1和他自身,那么这个数就是素数。例如:2,3,5,7
回文数:一个数如果从左往右读与从右往左读是一样的,那么这个数就是回文数。比如:12321
示例1:输入6,输出7
示例2:输入8,输出11
示例3:输入13,输出101
1<=N<=10^8
答案肯定存在,并且小于2*10^8
首先介绍三个数学定理:
要求解这个问题,我们可以将问题分成两个大问题,1.判定一个数是否是素数。2.求大于或等于一个数的最小回文数。
要判定一个数是否是素数,可以使用定理2(证明这里就不给了,百度都有。)。对于定理2,我们同时可以理解为所有素数的形式都可以写成6x+1 或 6x-1(这里的x是是大于等于1的自然数。)。所以判定一个数num是否是素数,我们可以先判定这个数num是否分布在6的两侧,如果不是则这个数num肯定不是素数。如果这个数num是分布在6的两侧,也不一定是素数,不一定是素数是因为两个在6同一侧的素数相乘,其结果还在6的那一侧,比如:55=25,25=64+1,所以我们还需要判定这个数num是否有6的两侧的数的因数。还有一个定理就是我们要判定一个数是否是素数,只需要判定小于这个数√num中是否有其因数。由于已经判定num是分布在6的两侧,所以我们就可以直接判断6的两侧的数是否是其因素。
代码:
//判断一个数是否是素数
/**
思路:一个大于6的素数必定定义写成6*i+1或6*i+5的形式,其中6*i+5可以写成6*i-1,因为+5到-1正好是一个6的轮回。
*/
func IsPrimeNum(num int) bool {
if num <= 3 {//如果num小于等于3时,只有2和3是素数
return num > 1
}
//3
if num%6 != 1 && num%6 != 5 {
return false
}
sq := int(math.Sqrt(float64(num)))
for i := 5; i <= sq; i += 6 {
if num%i == 0 || num%(i+2) == 0 {
return false
}
}
return true
}
求大于等于一个数的最小回文数,我们可以先将数字num转换成字符串(容易对每一位操作)。在求回文数之前,这里首先说明一个点:大于数字num的最小回文数numPa,当num长度是奇数时,num修改地方一定在[len(num)/2, len(num)];当num长度是奇数是,num一定需要修改的地方是[len(num)/2 -1, len(num)]。也就是说数字num的一半right是一定会修改的,并且是比原来的大。对于数字num,num的长度不同时求回文数的处理是不同的。
当num的长度是偶数时:想要求大于num的最小回文数,我们需要将数字分成两个部分,leftNum数字num左半部分和rightNum数字num右半部分,以及reverseLeftNum数字num左半部分反转。比如123456,leftNum = 123, rightNum = 456, reverseLeftNum=321。num长度是偶数时,如果我们直接将数字num的左边反转到右边,可以发现得到的回文数是小于num,其原因就是reverseLeftNum是小于rightNum,所以就会导致求出来的结果小于原来的数num。但是当reverseLeftNum大于rightNum时呢,比如数字437123,reverseLeftNum=734,rightNum=123,直接将数字左半部反转给右半部得到的是437734,是大于原来的数字的。所以这里我们可以得出结论:当num长度是偶数时,如果reverseLeftNum大于时,那么大于num的最近的回文数就是leftNum+reverseLeftNum;如果reverseLeftNum小于rightNum时,我们需要对leftNum++,然后再反转leftNum给reverseLeftNum,这是reverseLeftNum肯定就大于rightNum,那么大于num的最近的回文数就是leftNum+reverseLeftNum。
当num长度是奇数时,需要把数字拆分成三部分,leftNum,middle和rightNum,只需要将middle+1,然后把leftNum反转给rightNum即可。当middle是9时,leftNum++,middle赋值成0,然后把leftNum反转给rightNum。
代码:
//获取大于等于数字最近的最近的回文数
func GetNearestPaNum(strNum string) string {
if IsPalindromeNum(strNum) {
return strNum
}
l := len(strNum)
var leftNum, reverseLeftNum, rightNum, middle int64
var leftStr, reLeftStr, rightStr string
//
/*
将数字拆分成三部分,left和right以及middle,其中left的len和right的len是相等的,
如果数字的长度是奇数,则middle的值为数字最中间那个值,如果数字长度是偶数的,则middle的值为-1,即表示不存在
*/
if l%2 == 0 {
leftNum, _ = strconv.ParseInt(strNum[:l/2], 10, 64)
reLeftStr = getReverseStr(strNum[:l/2])
reverseLeftNum, _ = strconv.ParseInt(reLeftStr, 10, 64)
middle = -1
rightNum, _ = strconv.ParseInt(strNum[(l)/2:], 10, 64)
leftStr = strNum[:l/2]
rightStr = strNum[(l)/2:]
} else {
leftNum, _ = strconv.ParseInt(strNum[:(l-1)/2], 10, 64)
reLeftStr = getReverseStr(strNum[:(l-1)/2])
reverseLeftNum, _ = strconv.ParseInt(getReverseStr(strNum[:(l-1)/2]), 10, 64)
middle, _ = strconv.ParseInt(string(strNum[l/2]), 10, 64)
rightNum, _ = strconv.ParseInt(strNum[l/2+1:], 10, 64)
leftStr = strNum[:(l-1)/2]
rightStr = strNum[l/2+1:]
}
/*
再根据reverseLeftNum和rightNum的大小来判断,如果reverseLeftNum大于rightNum,则回文数的右半部就是reverseLeftNum
如果reverseLeftNum小于rightNum,则回文数的左半部leftNum需要+1,右半部为左半部的反转
*/
if reverseLeftNum > rightNum {
rightStr = reLeftStr
} else if reverseLeftNum < rightNum {
if middle == -1 {
leftNum++
leftStr = strconv.FormatInt(leftNum, 10)
rightStr = getReverseStr(leftStr)
} else if middle == 9 {
leftNum++
leftStr = strconv.FormatInt(leftNum, 10)
rightStr = getReverseStr(leftStr)
middle = 0
} else {
middle++
rightStr = reLeftStr
}
}
//左右拼接
if middle != -1 {
return leftStr + strconv.FormatInt(middle, 10) + rightStr
} else {
return leftStr + rightStr
}
}
再根据定理1,我们就可以跳过长度为偶数的回文数。
代码:
/**
思路: 需要用到两个定理:1.偶数位的回文数都能被11整除。2.除了2和3以为,所有的素数都分布在6两侧
如果当前数字的长度是偶位数,其长度是l,则我们可以从10000...001处进行判断,其长度为l+1,也就是我们可以跳过偶数的回文数
*/
func PrimePalindrome(N int) int {
if N == 10 || N == 11 {
return 11
}
nStr := strconv.FormatInt(int64(N), 10) //将数字转为字符串
l := len(nStr)
if l < 2 { //如果n的长度为1,
for !IsPrimeNum(N) && l == 1 {
N++
nStr = strconv.FormatInt(int64(N), 10)
l = len(nStr)
}
if l == 1 { //上面的for是因为N的长度大于1而跳出的
return N
}
return 11
}
//长度大于1,需要根据第一个定理进行判断
if l%2 == 0 { //N的长度为偶数
N = int(math.Pow10(l) + 1) //让N从长度为偶数位开始跳过一些数字
nStr = strconv.FormatInt(int64(N), 10)
}
nStr = GetNearestPaNum(nStr) //找到N最近的回文数,包括本身
n64, _ := strconv.ParseInt(nStr, 10, 64)
N = int(n64)
for !IsPrimeNum(N) {
N++
nStr = GetNearestPaNum(strconv.FormatInt(int64(N), 10))
l = len(nStr)
n64, _ = strconv.ParseInt(nStr, 10, 64)
N = int(n64)
if l%2 == 0 {
N = int(math.Pow10(l) + 1) //让N从长度为偶数位开始跳过一些数字
nStr = strconv.FormatInt(int64(N), 10)
} else {
//跳过偶数的回文数,比如808这个回文数,其大于他最近的回文数是818,明显不是素数,所以可以跳过成909,
//有个数学定理,如果一个数字的尾数可以整除2,那么这个数字必定可以整除2
last, _ := strconv.ParseInt(string(nStr[l-1]), 10, 64)
if last%2 == 0 { //尾数可以整除2
last++
str := strconv.FormatInt(last, 10)
strByte := []byte(nStr)
strByte[0] = byte(str[0])
strByte[len(strByte)-1] = strByte[0]
nStr = string(strByte)
}
}
}
return N
}
//获取大于等于数字最近的最近的回文数
func GetNearestPaNum(strNum string) string {
if IsPalindromeNum(strNum) {
return strNum
}
l := len(strNum)
//num, _ := strconv.ParseInt(strNum, 10, 64)
var leftNum, reverseLeftNum, rightNum, middle int64
var leftStr, reLeftStr, rightStr string
//
/*
将数字拆分成三部分,left和right以及middle,其中left的len和right的len是相等的,
如果数字的长度是奇数,则middle的值为数字最中间那个值,如果数字长度是偶数的,则middle的值为-1,即表示不存在
*/
if l%2 == 0 {
leftNum, _ = strconv.ParseInt(strNum[:l/2], 10, 64)
reLeftStr = getReverseStr(strNum[:l/2])
reverseLeftNum, _ = strconv.ParseInt(reLeftStr, 10, 64)
middle = -1
rightNum, _ = strconv.ParseInt(strNum[(l)/2:], 10, 64)
leftStr = strNum[:l/2]
rightStr = strNum[(l)/2:]
} else {
leftNum, _ = strconv.ParseInt(strNum[:(l-1)/2], 10, 64)
reLeftStr = getReverseStr(strNum[:(l-1)/2])
reverseLeftNum, _ = strconv.ParseInt(getReverseStr(strNum[:(l-1)/2]), 10, 64)
middle, _ = strconv.ParseInt(string(strNum[l/2]), 10, 64)
rightNum, _ = strconv.ParseInt(strNum[l/2+1:], 10, 64)
leftStr = strNum[:(l-1)/2]
rightStr = strNum[l/2+1:]
}
/*
再根据reverseLeftNum和rightNum的大小来判断,如果reverseLeftNum大于rightNum,则回文数的右半部就是reverseLeftNum
如果reverseLeftNum小于rightNum,则回文数的左半部leftNum需要+1,右半部为左半部的反转
*/
if reverseLeftNum > rightNum {
rightStr = reLeftStr
} else if reverseLeftNum < rightNum {
if middle == -1 {
leftNum++
leftStr = strconv.FormatInt(leftNum, 10)
rightStr = getReverseStr(leftStr)
} else if middle == 9 {
leftNum++
leftStr = strconv.FormatInt(leftNum, 10)
rightStr = getReverseStr(leftStr)
middle = 0
} else {
middle++
rightStr = reLeftStr
}
}
//左右拼接
if middle != -1 {
return leftStr + strconv.FormatInt(middle, 10) + rightStr
} else {
return leftStr + rightStr
}
}
func getReverseStr(str string) string {
s := ""
for i := len(str) - 1; i >= 0; i-- {
s += string(str[i])
}
return s
}
//检查一个数是否是回文数
func IsPalindromeNum(strNum string) bool {
l := len(strNum)
for i, j := 0, l-1; i < j; i, j = i+1, j-1 {
if strNum[i] != strNum[j] {
return false
}
}
return true
}
//判断一个数是否是素数
/**
思路:一个大于6的素数必定定义写成6*i+1或6*i+5的形式,其中6*i+5可以写成6*i-1,因为+5到-1正好是一个6的轮回。
*/
func IsPrimeNum(num int) bool {
if num <= 3 {//如果num小于等于3时,只有2和3是素数
return num > 1
}
//3
if num%6 != 1 && num%6 != 5 {
return false
}
sq := int(math.Sqrt(float64(num)))
for i := 5; i <= sq; i += 6 {
if num%i == 0 || num%(i+2) == 0 {
return false
}
}
return true
}
该算法通过领扣的测试用例,并且用时最少,消耗内存最低。