题目
给定一个整数 n ,你需要找到与它最近的回文数(不包括自身)。
“最近的”定义为两个整数差的绝对值最小。
示例 1:
输入: “123”
输出: “121”
注意:
n 是由字符串表示的正整数,其长度不超过18。
思路分析
一个数字附近的回文数有很多个,想要寻找最近的一个回文数,我们不可能通过遍历其所有回文数来达到目的。所以这时候就需要给这个数的相近回文数一个范围,查找这个范围内的回文数,然后进行比较获取最近的一个。
数字123,最近的回文数是121;100最近的回文数是99,999最近的回文数1001;通过举例不难发现,一个数的相近的回文数有可能比这个数少一位,有可能比这个数多一位,我们可以根据这个来圈出一个范围[99,1001]。比如123,比123小的回文数有121、111、101、99(99-123),比123大的回文数有131、141、151、161、171、181、191…1001,这么多个,可以比较这些回文数与123的差,和回文数的大小来确定最近的是121。
确定了两个边界后,怎么确定边界内的回文数呢。由于题目要求的是最近的回文数,我们知道当两个数高位相同时,低位越相近,则这两个数就越相近。
1.n不是回文数
比如123,最近的回文数是121。所以我们可以保留n的高位(比如123,保留12*)。但是回文数是一个数左右对称,所以低位就是高位的反序,所以低位可以保留赋值为**1(121)。上面这种情况是当n不是回文数的时候,如果当n是回文数时,上面的方法就不成立(题目要求不能是本身)
2.n是回文数
对于例子222,由于本身已经是一个回文数,所以我们就不需要再对第一位和第三位进行修改,只需要对第二位中间那位进行修改。题目要求最近并且最小的回文数,对于222有两个最近的回文数:232、212,最小的是212,所以其最近回文数为212。但是对于999来说,最近的回文数却是1001(989距离999更远)。
这就出现了两种情况了,一种是对n中间那个数-1,一种是+1。但是我们统一n不是回文数这种情况来看,123->121 就是12 +0 然后再将低位转成对应的高位1成121;222->212 就是22-1 再拼接原本的低位2;999->1001 就是99+1 然后再将低位转成对应的高位1成1001。
所以我们所需要做的操作就是对n的中间那位进行-1、+0、+1操作,然后将低位赋值为高位的逆序。这里举的例子都是n长度为奇数时的,当n为偶数时也是同样的情况,只不过高位取一半,而不是奇数时取了(len(n)+1)/2。
算法代码
/**
思路分析:求一个数的最近回文数,比如给了131,就要返回121.而且返回的回文数位数和n可能还不相同,比如n=100,则返回99。n=99,
返回101.那么就可以确定了最近回文数的一个范围,比如n为三位数123,则其回文数的范围在[99, 1001]之间。所以我们就可以就给出的边界值
和求出的其他回文数进行比较远近和大小。
边界值是两种情况,身下的其他的回文数我们可以通过数字n本身求出来。我们知道回文数是一个数左右相互对称,比如12344321。为了将n改成
回文数,我们可以改变高位(低位不变),使得高位和低位相同;或者改变低位(高位不变),使得低位和高位相同。但是这里我们需要求最近的回文数,
所以我们只需要高位值不变改变低位(因为改变高位,会使得值变动的更大)。
对于回文数 12534,为了求最近的回文数,我们可以先将数字切割成左右两块(左边的包含中心那个数字):left=124,right=34。
此时我们需要将n区分为是回文数,和不是回文数两种。如果n是回文数,我们需要改变n的中间那个值。这样我们只需要对中心那个数字+1 -1操作(left-1 = 125,left+1 = 123),
然后right反转left即可(这里要区分n长度是奇偶,奇数时不需要left最后一个,偶数时全部反转即可)。
当n不是回文数,我们只需要使得right等于left的逆序即可(这里同样需要考虑奇偶)。
所以总结来说,我们只需要对left做-1 +0 +1 的操作(其中-1 +1是n为回文数时),就可以求出最近回文数的一个集合,然后再与边界值相比较即可
*/
func nearestPalindromic(n string) string {
lenN := int64(len(n)) //n的长度
nNum, err := strconv.ParseInt(n, 10, 64) //将n转换成int64
if err != nil {
return "-1"
}
//直接判断1001 和99 类似的值谁距离n最近
min := int64(0) //用于存储当前最小回文数与n的差值
minPa := int64(0) //当前最小回文数
num101 := int64(math.Abs(math.Pow10(int(lenN)) + 1.0 - float64(nNum))) //求类似101 的回文数与n的差的绝对值
num99 := int64(math.Abs(math.Pow10(int(lenN-1)) - 1.0 - float64(nNum))) //求类似99 的回文数与n的差的绝对值
if int64(math.Pow10(int(lenN)+1)) < 0 { //如果类型101的回文数超出int64上限,则直接将当前最近回文数设置为类似99
min = int64(num99)
minPa = int64(math.Pow10(int(lenN) - 1))
} else {
//判断类似101和99 的回文数哪个距离n最近
if num101 > num99 {
min = int64(num99)
minPa = int64(math.Pow10(int(lenN-1)) - 1)
} else {
min = int64(num101)
minPa = int64(math.Pow10(int(lenN)) + 1)
}
}
leftNum, _ := strconv.ParseInt(n[:(lenN+1)/2], 10, 64) //获取左半部的值(1234 left=12,12345 left = 123)
for i := -1; i <= 1; i++ {
leftStr := strconv.FormatInt(leftNum+int64(i), 10) //左半部字符串
rightStr := "" //右半部分字符串,如果lenN为奇数,则最中间那个值不需要
for j := int64(len(leftStr)-1) - lenN%2; j >= 0; j-- { //反向遍历leftStr获取右半部
rightStr += string(leftStr[j])
}
str := leftStr + rightStr //回文数字符串
if n == str { //移除是自己的情况
continue
}
num, _ := strconv.ParseInt(str, 10, 64)
//计算距离n最小的回文数
mu := int64(math.Abs(float64(num - nNum)))
if mu < min {
min = mu
minPa = num
} else if mu == min && num < minPa {
minPa = num
}
}
return strconv.FormatInt(minPa, 10)
}
使用测试用例n=955166082267348992,进行go基准测试得到的结果为:执行了1000000次,每次消耗时间平均为:1300 ns/op。并且在领扣代码提交中耗时0ms,击败了100%的提交的go语言代码。