Problem overview
The method isPalindrome receives a string, input, and returns true when it reads the same forwards and backwards. Every character counts, including spaces, and uppercase and lowercase letters are different characters.
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 the ends and walk inward
You could build the reversed string and compare it with the original. That costs a second copy of the data. The two-pointer idea needs no reversed copy: a palindrome has s[0] == s[n - 1], s[1] == s[n - 2], and so on. So put one pointer, left, at the start and another, right, at the end. Compare the characters they point at, and move both pointers one step toward the middle. If any pair differs, the string isn't a palindrome. If the pointers meet or cross without a mismatch, it is.
Only half of the characters need to be examined, because each comparison covers two of them. When the length is odd, the middle character has no partner. It only matches itself, so it is never compared.
Step by step
The starting code already turns the string into an array of characters, because Swift strings can't be indexed with an integer: input[2] is a compile error. The array chars can be indexed with integers.
- Create
leftas avarstarting at 0, the first index. It must be a variable, because it changes in the loop. - Create
rightas avarstarting atchars.count - 1, the last index. - Loop while
left < right. While the pointers haven't met, there is a pair left to compare. - Compare the pair. If
chars[left] != chars[right], returnfalseat once. One mismatch settles the answer. - Move the pointers. Add 1 to
leftand subtract 1 fromright. Without this, the loop would compare the same pair forever. - Return
trueafter the loop. Every pair matched.
The solution in Swift
class Solution {
func isPalindrome(_ input: String) -> Bool {
let chars = Array(input)
var left = 0
var right = chars.count - 1
while left < right {
if chars[left] != chars[right] {
return false
}
left += 1
right -= 1
}
return true
}
}
Swift details worth knowing:
Array(input)makes a[Character]. A SwiftCharacteris what a reader sees as one character, even when it is built from several Unicode scalars. A letter followed by a combining accent is oneCharacter. ComparingCharactervalues therefore compares what the reader sees.input.countcountsCharactervalues too.- Why not index the string? Characters have different sizes in memory, so Swift can't jump to the n-th one in constant time. It makes you convert to an array, or step through
String.Indexvalues. !=works on characters, as on numbers. Nothing like a.equalsmethod is needed.- An empty string. Then
chars.count - 1is -1 andleft < rightis false at once, so the method returnstrue. Swift'sIntcan be negative, so no arithmetic goes wrong here. - The early
returninside the loop leaves the whole method, not just the loop.
A variant without the array
To reach the O(1) extra space in the statement, move two String.Index values instead of integers:
func isPalindromeIndex(_ input: String) -> Bool {
var left = input.startIndex
var right = input.endIndex
while left < right {
right = input.index(before: right)
if left == right { break }
if input[left] != input[right] {
return false
}
left = input.index(after: left)
}
return true
}
endIndex points one past the last character, so right is moved back before each comparison. The left == right check stops the loop when both indices meet on the middle character of an odd-length string. It returns the same answers as the lesson's version for "abba", "aba", "abcbx" and the empty string.
Complexity
Time: O(n). Building the array takes one pass, and the loop makes at most n / 2 comparisons.
Space: O(n) in this version. Array(input) copies the characters. The two pointers add only O(1). The statement asks for O(1) extra space, which a string-index version could reach by moving two String.Index values toward each other, but that is harder to read. This lesson trades the extra memory for simple integer indexing.
Edge cases and common mistakes
- Empty string and one character. Both are palindromes. For
""and"a"the loop never runs and the method returnstrue. - Odd and even lengths.
"abba"ends withleftequal to 2 andrightequal to 1, and"aba"ends with both at 1. Both stop correctly. - Case and spaces matter.
"Madam"returnsfalsebecauseMandmdiffer."race car"returnsfalsebecause its spaces are compared like other characters, so the firstrmeets the lastr, thenameetsa, and thencmeets the space. - Using
<=instead of<. On an odd-length string the pointers land on the same middle character, which compares equal to itself, so the result is the same. The extra comparison is useless, and<says what is meant: compare pairs. - Forgetting to move a pointer. If only one pointer moves, the comparisons are wrong, and if neither moves, the loop never ends.
- Writing
input[i]. It doesn't compile. Use thecharsarray.
Testing
"madam racecar madam"givestrue."abcbx"givesfalse.""givestrue."a"givestrue."abba"givestrue."Madam"givesfalse."race car"givesfalse.
