Does a banner read the same both ways?

Two pointers Junior Python 3.13+ Kotlin 2.4+ Reviewed By Best Latest Apps

Predict the output

What does this print?

Python 3.13
s = "No lemon, no melon"
t = [c.lower() for c in s if c.isalnum()]
print(t == t[::-1], len(t))

Options

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A True 14

Right: 14 letters remain and they read the same in both directions.

Filtering drops three spaces and a comma, leaving 'nolemonnomelon', which mirrors itself.

Why not the others?
  • B False 18. 18 is the raw length with spaces and the comma; those are filtered out.
  • C True 18. The filter drops 4 characters, so the length is 14.

30-second answer

Walk two pointers inward from both ends. Skip characters that are not letters or digits; otherwise compare the two characters case-insensitively and stop at the first mismatch. O(n) time and O(1) space, without building a cleaned copy of the string.

Practise saying it · 30 s. No recording, just a timer

Explain the inward two-pointer check.

Say your answer out loud, then check which of these points you covered:

  • Skip non-alphanumerics
  • Case-insensitive compare
  • O(1) extra space

Deep dive

The skip steps move one pointer at a time, so each character is looked at a bounded number of times: still O(n).

Building cleaned = ''.join(...) and comparing with cleaned[::-1] is clear and also O(n), but uses O(n) memory.

str.isalnum() accepts Unicode letters and digits; say so if the interviewer means ASCII only.

Python solution Ran on Python 3.13.5 · mypy --strict
Python 3.13
def reads_both_ways(banner: str) -> bool:
    lo, hi = 0, len(banner) - 1
    while lo < hi:
        if not banner[lo].isalnum():
            lo += 1
        elif not banner[hi].isalnum():
            hi -= 1
        elif banner[lo].lower() != banner[hi].lower():
            return False
        else:
            lo, hi = lo + 1, hi - 1
    return True

Kotlin Ran on Kotlin 2.4.21 · JDK 21.0.12.1
Kotlin 2.4
fun readsBothWays(banner: String): Boolean {
    var lo = 0
    var hi = banner.length - 1
    while (lo < hi) {
        val a = banner[lo].lowercaseChar()
        val b = banner[hi].lowercaseChar()
        when {
            !a.isLetterOrDigit() -> lo++
            !b.isLetterOrDigit() -> hi--
            a != b -> return false
            else -> { lo++; hi-- }
        }
    }
    return true
}

Common mistakes

  • Lowercasing the whole string first: fine for clarity, but it allocates a copy.
  • Forgetting that an empty or all-punctuation banner counts as reading the same both ways.

Senior take & follow-ups

Mention both the in-place and the cleaned-copy versions with their space costs, then write the in-place one.

Follow-up questions

Think of your answer first, then open the hint.

  1. What if you may delete one character?

    Show hint

    On the first mismatch, check whether skipping the left or the right character leaves a match.

  2. How would you check a linked list?

    Show hint

    Find the middle with fast and slow pointers, reverse the second half, compare.

What each operation costs: Arrays · Array Operations

From the author

DSA Master - Data & Algorithms

Free on iPhone and iPad: watch 50+ data structures and algorithms step through animations, run the code in the playground, and try the daily challenge.

Suggest a fix if something here is wrong or out of date.