Problem overview
The function is_palindrome receives a String named input and returns true when the text reads the same backward as forward, and false otherwise. Every character counts, spaces included.
The string "madam racecar madam" is a palindrome: reversed, it gives the same text, so the answer is true. The string "abcbx" starts with a and ends with x, so the answer is false.
Approach: compare from both ends
The easy way to test for a palindrome is to build the reversed text and compare it with the original. That works, but it needs a full second copy and does more work than necessary. A palindrome has a simpler description: the first character equals the last one, the second equals the second to last, and so on up to the middle.
That suggests two pointers. Put left at the first character and right at the last. While left is before right, compare the two characters. If they differ, the answer is false at once. If they match, move left one step right and right one step left, and repeat. When the pointers meet or cross, every pair has matched, and the answer is true.
For "abcbx", the first comparison is a against x. They differ, so the function returns false after a single check. For "abcba" the pairs a, a and b, b match, then the pointers meet on the middle c. That middle character is never compared, which is fine because it matches itself.
Step by step
- Collect the characters into a vector. Rust strings can't be indexed by position, so
input.chars().collect()builds aVec<char>. The annotationVec<char>tellscollectwhich collection to build. - Store the length in
nwithchars.len(). It is ausize, the unsigned type for sizes and indexes. - Handle the empty string with an early
return truewhenn == 0. An empty text reads the same backward. The return also protects the next step. - Create
leftas a mutableusizeat 0. The suffix on0usizegives the literal its type. - Create
rightas a mutable variable atn - 1, the last index. On an empty string this would underflow, because ausizecan't go below zero. That is why step 3 comes first. - Loop while
left < right. As long as the pointers haven't met, there is a pair left to compare. - Compare the pair. If
chars[left] != chars[right], returnfalse. One mismatch settles the question. - Move both pointers inward: add 1 to
leftand subtract 1 fromright. Without this, the loop would examine the same pair forever. - Finish with
trueas the last expression, with no semicolon. It is reached only when every pair matched.
The solution in Rust
impl Solution {
fn is_palindrome(input: String) -> bool {
let chars: Vec<char> = input.chars().collect();
let n = chars.len();
if n == 0 {
return true;
}
let mut left = 0usize;
let mut right = n - 1;
while left < right {
if chars[left] != chars[right] {
return false;
}
left += 1;
right -= 1;
}
true
}
}
The Rust details worth a closer look:
- Why not index the string? Writing
input[0]on aStringdoesn't compile. The compiler reportsthe type `str` cannot be indexed by `{integer}`. A Rust string is UTF-8 text, and one character can take several bytes, so a position in bytes is not a position in characters. - Characters versus bytes. For
"été",len()is 5 bytes butchars().count()is 3 characters. Collectingchars()gives a vector with onecharper character, so the two pointers compare whole characters, and"été"is correctly a palindrome. collect()needs a type. It can build many kinds of collections, so the annotation oncharsis what selectsVec<char>.- Unsigned pointers. Both pointers are
usize. The loop conditionleft < rightguarantees thatrightis at least 1 whenright -= 1runs, so the subtraction can't go below zero. - Comparing chars.
charimplements!=, sochars[left] != chars[right]compares two values directly. No borrowing or cloning is needed, becausecharis a smallCopytype. - Early
returnversus the last expression. Leaving the function from inside a loop needsreturn. The finaltruehas no semicolon, so it is the function's value.
Complexity
Time: O(n). Building the vector takes one pass. The loop then runs at most n / 2 times, with constant work in each.
Space: O(n). The vector of characters holds one entry per character. The pointers use constant space. A version that walks the string's characters from both ends with chars() and next_back() avoids the vector and needs only O(1) extra space.
Edge cases and common mistakes
- Empty string. It returns
truefrom the early return. Without that return,n - 1would underflow: it panics in a debug build and wraps in an optimized one. - One character.
"a"givestrue:leftandrightboth start at 0, so the loop doesn't run. - Two characters.
"ab"givesfalseand"aa"givestrue. One comparison decides each. - Spaces and case count.
"a b a"is a palindrome, but"aa "is not, because the trailing space has no partner."Aba"is not a palindrome either, sinceAandaare different characters. - Non-ASCII text.
"日本日"givestrue, because the comparison is between characters rather than bytes. - Using
left <= right. For"a"both pointers start at 0, the loop body runs, andright -= 1subtracts 1 from ausizethat holds 0. A debug build panics there, and an optimized build wraps to a huge value. The strict comparison avoids it. - Forgetting to move the pointers. The loop would repeat the same comparison forever when the pair matches.
Testing
"madam racecar madam"givestrue."abcbx"givesfalse.""givestrue."abba"givestrue."Aba"givesfalse.
