All other factors being equal, the best solution to a problem is the one that's easiest for someone else to understand. You mention that you don't want to use anything but "basic loops and mathematical operations," but code that is easiest to understand usually does not contain raw, explicit loops. Therefore I suggest the best solution is simply:
bool is_palindrome = std::equal(in.begin(), in.end(), in.rbegin());
Of course, this does roughly twice as much work as is necessary, reading each character twice. Unless you have extremely long strings, this is unlikely to matter at all, but if you want to reduce that linear-time coefficient in half anyway, you can do so without losing much readability:
bool is_palindrome = std::equal(in.begin(), in.begin() + (in.size() / 2), in.rbegin());
All the edge cases here just do the right thing, but I'll go over them anyway, just for laughs and kicks.
In the case of an empty string, in.begin() == in.end()
, and std::equal
returns true
for empty ranges, so it is correctly considered a palindrome. In the optimized version, in.begin() + 0 == in.begin()
so we get the same thing.
In the case of a single character input, the first version compares it with itself, and (surprise!) finds that it's equal. In the optimized version in.size() / 2
becomes 1 / 2
, which when doing integer division, ends up as 0, so we're back to comparing an empty range, and it's considered a palindrome.
In the case of an input with odd length, the first version compares the character in the middle with itself, which is unnecessary but doesn't change the result. In the optimized version, again n / 2
truncates towards 0, so the middle character is ignored, and that's okay.
Since we know that the length counting from the beginning of the string is always going to be equal to the length if we start counting from the end of the string, we never need to worry about using the std::equal
overload that takes an end iterator for the second range or checking sizes explicitly.