Closest Palindrome Number


Closest Palindrome Number



I came across one of the common interview question which was to find the closest palindrome number. Say if the input is 127 then output will be 131 and if it is 125 then it should give 121 as output.



I can come up with the logic but my logic fails on certain cases like 91, 911. In these inputs it give 99 , 919 but the correct output is 88 and 909.



Algorithm steps are:





Sometimes there's a tie: the closest palindromes to 1000 are 1001 and 999. I think it can be established that there is always one closest palindrome that has the same number of digits. However, as you noted, the closest palindrome may share no digits in common with the number.
– Ted Hopp
Sep 24 '13 at 17:50






Folding the string as you describe gives one of the closest palindromes, either upper or lower depending. How do you get the other one?
– Adam Burry
Sep 24 '13 at 18:16





5 Answers
5



This is actually an interesting problem. Obviously what you want to do to make this more than just a brute force is to use the most significant digits and put them in the least significant digit locations to form a palindrome. (I'm going to refer to the difference between the palindrome and the original as the "distance")



From that I'm going to say that we can ignore the least significant half of the numbers because it really doesn't matter (it matters when determining the distance, but that's all).



I'm going to take an abstract number: ABCDEF. Where A,B,C,D,E,F are all random digits. Again as I said D,E,F are not needed for determining the palindrome as what we want is to mirror the first half of the digits onto the second half. Obviously we don't want to do it the other way around or we'd be modifying more significant digits resulting in a greater distance from the original.


ABCDEF



So a palindrome would be ABCCBA, however as you've already stated this doesn't always you the shortest distance. However the "solution" is still of the form XYZZYX so if we think about minimizing the "significance" of the digits we're modifying that would mean we'd want to modify C (or the middle most digit).


ABCCBA


XYZZYX



Lets take a step back and look at why: ABCCBA


ABCCBA


A


A


B


C



Okay so now that we've worked out that we want to modify C to get our potentially closer number we need to think about bounds. ABCDEF is our original number, and if ABCCBA isn't the closest palindrome, then what could be? Based on our little detour above we can find it by modifying C. So there are two cases, ABCDEF is greater than ABCCBA or that is less than ABCCBA.


C


ABCDEF


ABCCBA


C


ABCDEF


ABCCBA


ABCCBA



If ABCDEF is greater than ABCCBA then lets add 1 to C. We'll say T = C+1 so now we have a number ABTTBA. So we'll test to make sure that ABCDEF - ABCCBA > ABCDEF - ABTTBA
and if so we know that ABTTBA is the nearest palindrome. As any more modifications to C would just take us more and more distant.


ABCDEF


ABCCBA


C


T = C+1


ABTTBA


ABCDEF - ABCCBA > ABCDEF - ABTTBA


ABTTBA



Alternately if ABCDEF is less than ABCCBA then we'll subtract 1 from C. Let's say V = C-1. So we have ABVVBA, which just like above we'll test: ABCDEF - ABCCBA > ABCDEF - ABVVBA and you'll have the same solution.


ABCDEF


ABCCBA


C


V = C-1


ABVVBA


ABCDEF - ABCCBA > ABCDEF - ABVVBA



The trick is that ABCDEF is always between ABTTBA and ABVVBA and the only other palindrome between those numbers is ABCCBA. So you only have 3 options for a solution. and if you compare ABCDEF to ABCCBA you only need to check 2.


ABCDEF


ABTTBA


ABVVBA


ABCCBA


ABCDEF


ABCCBA



I don't think it will be hard for you to adapt this to numbers of any size. and in the case of an odd number of digits you'd simply have ABCBA, ABVBA and ABTBA and so on...


ABCBA


ABVBA


ABTBA



So just like your examples: lets take 911.


911 - 919


911 - 909



So this gives us a constant time algorithm :)



This appears to be what you have, but I thought I'd elaborate to hopefully shed light on the issue as it seems to be a small programming error on your part otherwise.





Very nice way of doing it! +1. How did you get to this approach?
– streppel
Sep 25 '13 at 13:25





@HappyYellowFace: I sat and thought about it :) Algorithms was one of my favorite subjects in my college studies
– Don
Sep 25 '13 at 19:45





Very well thought, man. I'll try to implement it as soon as I get some free time.
– streppel
Sep 25 '13 at 19:46





Adam Burry already has in another answer
– Don
Sep 26 '13 at 14:59



This is an implementation of Naveen's and Don's algorithm. It uses Happy Yellow Face's algorithm as a test oracle.



I would be happy to see people tweak it to remove redundant steps or special cases.



gcc 4.7.3: g++ -Wall -Wextra -std=c++0x nearest-palindrome.cpp


#include <algorithm>
#include <cassert>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>

// I do not have std::to_string.
template <class T>
std::string to_string(const T& v) {
std::stringstream ss;
ss << v;
return ss.str(); }

// Nor do I have std::stoi. :(
int stoi(const std::string& s) {
std::stringstream ss(s);
int v;
ss >> v;
return v; }

bool isPalindrome(int n) {
const auto s = to_string(n);
return s == std::string(s.rbegin(), s.rend()); }

int specNearestPalindrome(int n) {
assert(0 <= n);

int less = n, more = n;
while (true) {
if (isPalindrome(less)) { return less; }
if (isPalindrome(more)) { return more; }
--less; ++more; } }

std::string reflect(std::string& str, int n) {
std::string s(str);
s.resize(s.size() + n);
std::reverse_copy(std::begin(str),
std::next(std::begin(str), n),
std::next(std::begin(s), str.size()));
return s; }

bool isPow10(int n) {
return n < 10 ? n == 1 : (n % 10 == 0) && isPow10(n / 10); }

int nearestPalindrome(int n) {
assert(0 <= n);
if (n != 1 && isPow10(n)) { return n - 1; } // special case

auto nstr = to_string(n);
// first half, rounding up
auto f1 = nstr.substr(0, (nstr.size() + 1) / 2);
auto p1 = stoi(reflect(f1, nstr.size() / 2));

const auto twiddle = p1 <= n ? 1 : -1;
auto f2 = to_string((stoi(f1) + twiddle));
auto p2 = stoi(reflect(f2, nstr.size() / 2));

if (p2 < p1) { std::swap(p1, p2); }
return n - p1 <= p2 - n ? p1 : p2; }

int main() {
std::vector<int> tests = { 0, 1, 6, 9, 10, 11, 12, 71, 74, 79, 99, 100, 999, 1000, 9900, 9999, 999000 };

for (const auto& t : tests) {
std::cout <<
(nearestPalindrome(t) == specNearestPalindrome(t) ? "." : "X");
}
std::cout << std::endl;

return 0; }





isPalindrome could be improved. It is not necessary to copy the string in reverse order or to compare the full string.
– Adam Burry
Oct 8 '13 at 19:28



Here is a generic algorithm that would work1, although using brute-force:


int findNearestPalindrome(int n) {
int less = n;
int more = n;
while(true) {
if (isPalindrome(less)) return less;
if (isPalindrome(more)) return more;
--less;
++more;
}
}



WithinisPalindrome() function, all you need to do is convert the number to a string, and then compare the string with itself reversed.


isPalindrome()



1However, this wouldn't check for tie cases, like Ted Hopp commented. You'd have to make a few changes to make it tie-recognizable.





I agree, this is a fine specification. I think the next question is, can the nearest palindrome be calculated, or found more efficiently?
– Adam Burry
Sep 24 '13 at 18:06






See my answer for something more efficient. :)
– Don
Sep 24 '13 at 18:43


#include <iostream>
#include <cmath>
#include <functional>
#include <limits>
#include <sstream>

// for convience
using namespace std;
using ULL = unsigned long long int;

// calculate the number of digits
auto Len = (auto num) -> ULL {
return floor(log10(num)) + 1; };

// extract left half of number
auto Halfn = (auto num, auto olen) {
for (unsigned i = 0; i < olen / 2; num /= 10, ++i);
return num;
};

int main() {
ULL num; cin >> num;
// some basic checking
if (num < 10) {
cerr << "Error, enter a number >= 10";
return 0;
}

if (numeric_limits<ULL>::max() < num) {
cerr << "Error, number too largen";
return 0;
}
cout << ((auto num) {
auto olen = Len(num);
auto lhalf = Halfn(num, olen);

function<ULL(ULL)> palin = [olen] (auto lhalf) {
auto half = to_string(lhalf);
// this is the mirror string that needs to be
// appended to left half to form the final
// palindrome
auto tmp = half.substr(0, olen / 2);

// take care of a corner case which
// happens when the number of digits in
// the left half of number decrease, while
// trying to find a lower palindrome
// e.g. num = 100000
// left half = 100 , the value passed to the
// function palin, is 99. if all digits are 9
// then we need to adjust the count of 9,
// otherwise if i simply replicate it, i'll get
// 9999 but one more 9 is required for the
// correct output.

if (olen / 2 > tmp.size() &&
all_of(tmp.begin(), tmp.end(),
(auto c) { return '9' == c; })) {
tmp += '9';
}

// append, convert and return
half = half + string(tmp.crbegin(),
tmp.crend());
return stoull(half);
};

auto bpalin = palin(lhalf);
auto hpalin = palin(lhalf + 1);
auto lpalin = palin(lhalf - 1);

stringstream ss;
ss << "base palindrome = " << bpalin <<'n';
ss << "higher palindrome = "<<hpalin <<'n';
ss << "lower palindrome = " << lpalin <<'n';

// calculating absolute difference for
// finding the nearest palindrome

auto diffb = labs(bpalin - num);
auto diffh = labs(hpalin - num);
auto diffl = labs(lpalin - num);

auto nearest = (diffb < diffh) ?
(diffb < diffl) ? bpalin : lpalin :
(diffh < diffl) ? hpalin : lpalin;

ss << "nearest palindrome = "
<< nearest << endl;
return move(ss.str());
}(num));
} // end main


class Solution {
public String nearestPalindromic(String n) {
int order = (int) Math.pow(10, n.length()/2);
Long ans = Long.valueOf(new String(n));
Long noChange = mirror(ans);
Long larger = mirror((ans/order)*order + order+1);
Long smaller = mirror((ans/order)*order - 1 );
if ( noChange > ans) {
larger = (long) Math.min(noChange, larger);
} else if ( noChange < ans) {
smaller = (long) Math.max(noChange, smaller);
}
return String.valueOf( ans - smaller <= larger - ans ? smaller :larger) ;
}
Long mirror(Long ans) {
char a = String.valueOf(ans).toCharArray();
int i = 0;
int j = a.length-1;
while (i < j) {
a[j--] = a[i++];
}
return Long.valueOf(new String(a));
}


}






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

List of Kim Possible characters

Audio Livestreaming with Python & Flask

NSwag: Generate C# Client from multiple Versions of an API