良好的方法能使我们更好地发挥天赋的才能,而拙劣的方法则可能阻碍才能的发挥。——贝尔纳
题目
给定一个表示整数的字符串 n
,返回与它最近的回文整数(不包括自身)。如果不止一个,返回较小的那个。
“最近的”定义为两个整数差的绝对值最小。
难度:困难
分析
我第一次做出这题时,直接做成了“面向测试用例编程”,提交错误了十几次,不断地分类讨论。不过我还是想在这里记录一下每个出错用例,从而总结出正确的方法。容易想到的是为了使绝对值最小,我们应该尽可能从末位数修改,使其与首位数相等。以下是各种用例:
"1" 因为答案不能与自身相同,所以返回为"0"
"10" "9"和"11"均为回文数,"9"更小应该返回。注意"10"为两位而"9"为一位
"1283" 根据最开始的思路,得到"1221",但是"1331"更接近输入。应当考虑“中间”回文数相邻的回文数
“12389” 与上例相似,"12421"比"12321"更接近输入,看到同样是最中间的数字的变动影响结果
"1837722381" 与上例相似,"1837667381"比"1837777381"更接近输入,注意这里中间的两位数是减小的
"9009" "8998"比"9119"更接近输入,这里中间的数字是"00",但是仍可以向“外面”的数字“借位”。我做错的原因是只检查中间的两位,以为是"00"就只能改为"11"。同理,"99"也可以“进位”成"00",所以只考虑最中间的两位数或一位数是错误的。
经过观察和归纳,对答案的构建进行总结:
- 输入为个位数时,除了"0"的答案为"1",其余为输入减一
- 取出输入字符串的前半部分(如果位数为奇数则包括中间的数字)x,分别以x,x+1,x-1构建回文数(即补出右半部分),比较结果与输入的差值,注意答案不能与输入相同
- 特殊判断两种情况:增加一位数得到"10...01";减少一位数得到"9...9"
解答
class Solution {
public:
string nearestPalindromic(string n) {
int count=n.length();
// 一位数特殊讨论
if (count==1){
n[0]=n[0]=='0'?'1':n[0]-1;
return n;
}
string pre=n.substr(0,(count+1)/2); // 取前半部分带中心
bool valid=1-count%2;
string ans=create(pre,valid);
// 不能与原字符串相同
if (ans==n){
ans="0";
}
int x=stoi(pre);
// 以x-1构造
string s1=to_string(x-1);
string p1=create(s1,valid);
ans=compare(n,ans,p1);
// 以x+1构造
string s2=to_string(x+1);
string p2=create(s2,valid);
ans=compare(n,ans,p2);
// 加1位 10...01
s1=g1(count+1);
ans=compare(n,ans,s1);
// 减1位 9...9
s2=g9(count-1);
ans=compare(n,ans,s2);
return ans;
}
string compare(string& origin, string& s1, string& s2){
long o=stol(origin);
long n1=stol(s1);
long n2=stol(s2);
long x1=abs(n1-o);
long x2=abs(n2-o);
if (x1<x2){
return s1;
}else if (x2<x1){
return s2;
}else{
return n1<n2?s1:s2;
}
}
string g1(int x){
string ans="1";
for (int _=0;_<x-2;_++){
ans+="0";
}
ans+="1";
return ans;
}
string g9(int x){
string ans;
for (int _=0;_<x;_++){
ans+="9";
}
return ans;
}
string create(string& s, bool lastValid){
string ans=s;
int n=s.length();
for (int i=lastValid?n-1:n-2;i>=0;i--){
ans+=s[i];
}
return ans;
}
};