Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 4 – Two pointers

Two pointers on strings: palindromes and edits

Decide whether a code reads the same backwards after deleting at most one character: the problem, its examples and why trying every deletion is O(n²).

  • Intermediate
  • 25 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free intro, Pro: 5 of 25 minutes free
Free: The problem: does a code mirror after one deletion? · Trying every deletion makes the check quadratic. With a pass: the other 10 sections and 3 interview answers, about 20 minutes.

What you will learn

  • Check palindromes with at most one deletion in O(n) time
  • Prove that the linear check finds every answer one deletion allows
  • Compare strings typed with backspaces without building new strings
  • Handle case, normalisation and grapheme rules explicitly in comparisons
  • Decide whether two strings are exactly one edit apart with two pointers

Before you start

What you’ll learn · Before you start

What you will learn

  • Check palindromes with at most one deletion in O(n) time
  • Prove that the linear check finds every answer one deletion allows
  • Compare strings typed with backspaces without building new strings
  • Handle case, normalisation and grapheme rules explicitly in comparisons
  • Decide whether two strings are exactly one edit apart with two pointers

Before you start

On this page

A string is a palindrome: A string that reads the same forwards and backwards, such as level or kayak. Comparing characters from both ends towards the middle checks it in one pass. when it reads the same backwards, and two pointers check that by comparing characters from both ends towards the middle: about 100,000 comparisons for a code of 200,000 characters, O(n) time and O(1) extra memory, with no reversed copy. The same kind of scan handles small edits, such as deleting one character, comparing text typed with backspaces, or telling whether two strings are one edit apart.

The problem below allows one deletion. Trying every deletion costs about 2 × 10¹⁰ comparisons at that size, and the first mismatch gives the signal that leads to a linear answer.

The problem: does a code mirror after one deletion?

The problem

A shop prints product codes made of the lowercase letters a to z and the digits 0 to 9. A code nearly mirrors when deleting at most one character makes it read the same backwards. Write nearly_mirrors(code), which returns True or False. Limits: 1 ≤ n ≤ 200,000 characters. The codes are plain ASCII, so the rules of case and Unicode do not arise here; a field holding people’s names would need them.

  1. "level" gives True: it mirrors already, so no deletion is needed.
  2. "k2ayak" gives True: deleting the 2 leaves "kayak".
  3. "zx9yz" gives False: no single deletion makes it mirror.

The second example is why the problem allows one deletion: kayak mirrors and k2ayak does not, so a single misprinted character is enough to break a code that should read the same both ways. The three examples, one row each:

The codes level, k2ayak and zx9yz: level mirrors, k2ayak mirrors once the 2 is deleted, and no single deletion makes zx9yz mirror.levellevelmirrors already: Truek2ayakk2ayakdelete the 2, kayak: Truezx9yzzx9yzno single deletion works: FalseDashed: the deleted character

The three examples of the problem

Text description of the diagram

The figure shows the three codes of the problem, one row of character cells each.

  1. level: the cells l, e, v, e and l. It reads the same backwards already, so the answer is True.
  2. k2ayak: the cells k, 2, a, y, a and k, with the 2 drawn dashed because it is deleted. What is left, kayak, reads the same backwards, so the answer is True.
  3. zx9yz: the cells z, x, 9, y and z. No single deletion makes it read the same backwards, so the answer is False.

Checkpoint

Check: A code that mirrors after one deletion

  1. One question Does the code rotator7 nearly mirror?

    Choose one answer.

    Show the answer: One question

    Answer: True: deleting the 7 leaves rotator, which reads the same backwards

    rotator7 starts with r and ends with 7, so it does not mirror as it is. Deleting the 7 leaves rotator, which reads the same backwards, so the code nearly mirrors.

Trying every deletion makes the check quadratic

Check the code itself, then the code with each character deleted in turn: n + 1 palindrome checks of up to n/2 comparisons each. The method is plainly correct, and it costs O(n²). Step through it on the third example:

Interactive · line by line Trying every deletion of zx9yz Step through the brute force. It builds the code itself and each of its five deletions, then checks each one from both ends.

Program · trace/each_deletion_steps.py

code = "zx9yz"
tries = [code] + [code[:d] + code[d + 1 :] for d in range(len(code))]
answer = False
for text in tries:
    lo, hi = 0, len(text) - 1
    while lo < hi and text[lo] == text[hi]:
        lo += 1
        hi -= 1
    if lo >= hi:
        answer = True
        break
print(answer)

Variables

After line 12: code is 'zx9yz'; tries is ['zx9yz', 'x9yz', 'z9yz', 'zxyz', 'zx9z', 'zx9y']; answer is False; text is 'zx9y'; lo is 0; hi is 3.code'zx9yz'tries'zx9yz'0'x9yz'1'z9yz'2'zxyz'3'zx9z'4'zx9y'5answerFalsetext'zx9y'lo0hi3
  • just taken by the loop
  • changed in this step
  • passed by the loop
  • read next
  • where the loop stopped

Printed so far

False

Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.

The drawing shows the variables when the program ends, and the program's 47 steps are listed below.

Line run 12
Next line end
Loop pass –
Every step as text
  1. Start. No line has run yet, so there are no variables. Python starts with line 1.
  2. Line 1. code = "zx9yz" sets code to 'zx9yz'.
  3. Line 2. tries = [code] + [code[:d] + code[d + 1 :] for d in range(len(code))] runs.
  4. Line 2. tries = [code] + [code[:d] + code[d + 1 :] for d in range(len(code))] runs.
  5. Line 2. tries = [code] + [code[:d] + code[d + 1 :] for d in range(len(code))] runs.
  6. Line 2. tries = [code] + [code[:d] + code[d + 1 :] for d in range(len(code))] runs.
  7. Line 2. tries = [code] + [code[:d] + code[d + 1 :] for d in range(len(code))] runs.
  8. Line 2. tries = [code] + [code[:d] + code[d + 1 :] for d in range(len(code))] sets tries to ['zx9yz', 'x9yz', 'z9yz', 'zxyz', 'zx9z', 'zx9y'].
  9. Line 3. answer = False sets answer to False.
  10. Line 4. for text in tries: takes the item at position 0 of tries: text is 'zx9yz'.
  11. Line 5. lo, hi = 0, len(text) - 1 sets lo to 0 and sets hi to 4.
  12. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is true (lo is 0, hi is 4 and text is 'zx9yz'), so the body runs.
  13. Line 7. lo += 1 changes lo from 0 to 1.
  14. Line 8. hi -= 1 changes hi from 4 to 3.
  15. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is false (lo is 1, hi is 3 and text is 'zx9yz'), so the loop ends.
  16. Line 9. lo >= hi is false (lo is 1 and hi is 3), so Python skips the block under if lo >= hi:. The code itself does not mirror. z matches z, but x and y differ, so the scan stops with lo below hi.
  17. Line 4. for text in tries: takes the item at position 1 of tries: text is 'x9yz'.
  18. Line 5. lo, hi = 0, len(text) - 1 sets lo to 0 (it was 1) and hi keeps its value, 3.
  19. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is false from the start (lo is 0, hi is 3 and text is 'x9yz'), so the body never runs.
  20. Line 9. lo >= hi is false (lo is 0 and hi is 3), so Python skips the block under if lo >= hi:.
  21. Line 4. for text in tries: takes the item at position 2 of tries: text is 'z9yz'.
  22. Line 5. lo, hi = 0, len(text) - 1 lo keeps its value, 0 and hi keeps its value, 3.
  23. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is true (lo is 0, hi is 3 and text is 'z9yz'), so the body runs.
  24. Line 7. lo += 1 changes lo from 0 to 1.
  25. Line 8. hi -= 1 changes hi from 3 to 2.
  26. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is false (lo is 1, hi is 2 and text is 'z9yz'), so the loop ends.
  27. Line 9. lo >= hi is false (lo is 1 and hi is 2), so Python skips the block under if lo >= hi:.
  28. Line 4. for text in tries: takes the item at position 3 of tries: text is 'zxyz'.
  29. Line 5. lo, hi = 0, len(text) - 1 sets lo to 0 (it was 1) and sets hi to 3 (it was 2).
  30. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is true (lo is 0, hi is 3 and text is 'zxyz'), so the body runs.
  31. Line 7. lo += 1 changes lo from 0 to 1.
  32. Line 8. hi -= 1 changes hi from 3 to 2.
  33. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is false (lo is 1, hi is 2 and text is 'zxyz'), so the loop ends.
  34. Line 9. lo >= hi is false (lo is 1 and hi is 2), so Python skips the block under if lo >= hi:.
  35. Line 4. for text in tries: takes the item at position 4 of tries: text is 'zx9z'.
  36. Line 5. lo, hi = 0, len(text) - 1 sets lo to 0 (it was 1) and sets hi to 3 (it was 2).
  37. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is true (lo is 0, hi is 3 and text is 'zx9z'), so the body runs.
  38. Line 7. lo += 1 changes lo from 0 to 1.
  39. Line 8. hi -= 1 changes hi from 3 to 2.
  40. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is false (lo is 1, hi is 2 and text is 'zx9z'), so the loop ends.
  41. Line 9. lo >= hi is false (lo is 1 and hi is 2), so Python skips the block under if lo >= hi:.
  42. Line 4. for text in tries: takes the item at position 5 of tries: text is 'zx9y'.
  43. Line 5. lo, hi = 0, len(text) - 1 sets lo to 0 (it was 1) and sets hi to 3 (it was 2).
  44. Line 6. while lo < hi and text[lo] == text[hi]: tests its condition: it is false from the start (lo is 0, hi is 3 and text is 'zx9y'), so the body never runs.
  45. Line 9. lo >= hi is false (lo is 0 and hi is 3), so Python skips the block under if lo >= hi:.
  46. Line 4. for text in tries: finds no item at position 6 of tries (it has 6 items), so the loop ends.
  47. Line 12. print(answer) prints False. That was the last line, so the program ends. Six candidates were built and checked, n + 1 for n = 5, and none mirrors. On a code of 200,000 characters the same plan makes about 2 × 10¹⁰ comparisons.

Trying every deletion, with the comparisons counted Python · brute/each_deletion.py
"""The first answer: check the code, then the code without each character in turn. It counts the character
comparisons of the palindrome checks."""


def nearly_mirrors_slow(code):
    comparisons = 0

    def mirrors(text):
        nonlocal comparisons
        lo, hi = 0, len(text) - 1
        while lo < hi:
            comparisons += 1
            if text[lo] != text[hi]:
                return False
            lo += 1
            hi -= 1
        return True

    if mirrors(code):
        return True, comparisons
    for d in range(len(code)):
        if mirrors(code[:d] + code[d + 1:]):
            return True, comparisons
    return False, comparisons


for code in ["level", "k2ayak", "zx9yz"]:
    answer, comparisons = nearly_mirrors_slow(code)
    print(f"{code!r}: {answer}, {comparisons} comparisons")

print()
print("The worst case: one letter repeated, three different characters in the middle (no single deletion works)")
for n in [1_000, 2_000, 4_000]:
    half = (n - 3) // 2
    code = "a" * half + "bcd" + "a" * (n - 3 - half)
    answer, comparisons = nearly_mirrors_slow(code)
    print(f"n = {n:>5,}: {answer}, {comparisons:>10,} comparisons")
n = 200_000
print(f"n = {n:,}: about {n * n // 2:,} comparisons")

Output

'level': True, 2 comparisons
'k2ayak': True, 5 comparisons
'zx9yz': False, 10 comparisons

The worst case: one letter repeated, three different characters in the middle (no single deletion works)
n = 1,000: False,    499,001 comparisons
n = 2,000: False,  1,998,001 comparisons
n = 4,000: False,  7,996,001 comparisons
n = 200,000: about 20,000,000,000 comparisons

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 each_deletion.py

Reverse Text, Mirror & Upside Down Reverse a code of your own to see where it stops reading the same backwards.

The worst case is a code that needs two deletions, such as one letter repeated with three different characters in the middle: every check runs to the middle before it fails. The comparisons of this brute force: The simplest method that is plainly correct, usually trying every possibility. Said first in a round, it confirms what the problem asks, and later it checks a faster version's answers. More in “A six-step method for any coding problem” grow as n²/2, so at 200,000 characters they reach about 2 × 10¹⁰, where a single scan from both ends makes about 10⁵.

Checkpoint

Check: Trying every deletion, and what it costs

  1. One question By the recorded counts, how many comparisons does the try-every-deletion brute force make on its worst code of 1,000 characters?

    Type a number.

    A hint

    Every one of the 1,001 checks runs to the middle before it fails.

    Show the answer: One question

    Answer: 499001 comparisons

    Each of the 1,001 palindrome checks runs to the middle of the code before failing, about 500 comparisons each, so the total is about n²/2: 499,001 for n = 1,000 and four times as many for each doubling.

What to notice: the first mismatch decides

A palindrome check stops at the first pair that differs, and every pair outside that point has already matched. A single deletion can only help if it changes which characters face each other at that first mismatch, and that points at the two characters of the mismatched pair. The figure shows the first mismatch of the second example:

The code k2ayak compared from both ends: the outer k and k match, then 2 and a differ at positions 1 and 4.k2ayak from both endsThe comparisonsk=2≠a?y?a≠k=0 and 5: k = k, a match1 and 4: 2 ≠ a, the first mismatch= matched, ≠ differs, ? not compared yet

Comparing k2ayak from both ends, up to the first mismatch

Text description of the diagram

The diagram has two rows, one under the other.

  1. The code k2ayak is drawn as six cells, each with a mark under its character. The first and last cells, k and k, are marked with an equals sign: they matched. The cells at positions 1 and 4, the digit 2 and the letter a, are marked with a not-equals sign: they differ. The two middle cells, a and y, are marked with a question mark: they were not compared yet.
  2. The comparisons are listed in order: positions 0 and 5 hold k and k, which match; positions 1 and 4 hold 2 and a, which differ, and that is the first mismatch. A last line explains the three marks.

Checkpoint

Check: The first mismatch decides

  1. One question Comparing k2ayak from both ends, which pair is the first mismatch?

    Choose one answer.

    Show the answer: One question

    Answer: Positions 1 and 4: the 2 against an a

    The scan compares k with k, a match, then the 2 at position 1 with the a at position 4, which differ. That first mismatch is where a single deletion has to help, as the figure shows.

These questions start with your weakest idea.

Practice that adapts to you

Practise this lesson

9 questions about 11 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 When does a code nearly mirror?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: When deleting at most one character makes it read the same backwards

    A code nearly mirrors when deleting at most one character makes it read the same backwards, so level, which mirrors already, counts too.

  2. Question 2 of 9 Does the one-character code q nearly mirror?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: True: it reads the same backwards already

    Any code of one character reads the same backwards, so no deletion is needed and the answer is True.

  3. Question 3 of 9 The problem limits codes to ASCII letters and digits. What does that rule out?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: Questions of case and Unicode, such as accents or flags, so each index is one whole character

    With lowercase letters and digits only, there is no case to fold and no character made of several code points, so comparing one index with another compares two whole characters.

  4. Question 4 of 9 At most how many candidate strings does the brute force check for a code of n = 6 characters?

    Type a number.

    A hint

    The code itself, then one deletion at each position.

    Show the answer: Question 4 of 9

    Answer: 7 candidates

    It checks the code itself and then the code without each of its 6 characters: n + 1 = 7 candidates, each checked from both ends.

  5. Question 5 of 9 Which code makes the brute force do the most work for its length?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: One letter repeated, with three different characters in the middle

    Its checks all run to the middle before they fail, so the work is n + 1 checks of about n/2 comparisons: about n²/2 in all, as the recorded counts show.

  6. Question 6 of 9 Why does the brute force fail at 200,000 characters, although it is plainly correct?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: Its worst case makes about 2 × 10¹⁰ comparisons, against about 10⁵ for a single scan from both ends

    n + 1 checks of up to n/2 comparisons grow as n²/2: about 2 × 10¹⁰ at n = 200,000. A single scan from both ends makes about 10⁵.

  7. Question 7 of 9 When a palindrome check stops at its first mismatch, what is known about the pairs outside it?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: They all matched

    The check compares pairs from the outside in and stops at the first pair that differs, so every pair before it matched. Only the pair where it stopped is in question.

  8. Question 8 of 9 Why can a single deletion only help if it changes which characters face each other at the first mismatch?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: Otherwise the same two different characters still face each other, and the code still does not mirror

    If the deletion leaves the mismatched pair facing each other, the code still has that mismatch. That points at the two characters of the pair as the ones a deletion has to deal with.

  9. Question 9 of 9 At most how many comparisons does the plain palindrome check make on a code of 1,000,001 characters?

    Type a number.

    A hint

    One comparison per pair of positions, from both ends towards the middle.

    Show the answer: Question 9 of 9

    Answer: 500000 comparisons

    The check compares positions 0 and 1,000,000, then 1 and 999,999, and so on: 500,000 pairs. The middle character, at position 500,000, faces itself and needs no comparison.

Two tries at the first mismatch, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • A dry run on two codes
  • A proof that two tries are enough
  • The code in 14 languages
  • Two shortcuts that fail, found by trying every short code
  • Backspaces, compared from the right
  • Case, accents and Unicode, decided before you code
  • Up to k deletions: branching and a cache
  • Practise: one edit apart
    • Tell whether two codes are exactly one edit apart
    • A practice set with sample answers
  • Interview questions
    • A candidate decides in O(n) whether a string becomes a palindrome after deleting at most one character, by trying only a few deletions. Which deletions must be tried, and why can no other deletion be the only fix? Prove it.
    • Generalise the one-deletion palindrome check to at most k deletions, for strings of up to 10⁵ characters and k up to 100. How does the cost grow with k, what does caching change, and what would you use for the largest inputs?
    • A product team wants a palindrome check for display names in any language, ignoring case and punctuation. Where does comparing code points after lower() go wrong, and what would you compare instead? Give concrete inputs.
  • Key takeaways

3 diagrams · 8 runnable examples (Python) · 1 exercise with sample tests · 3 interview answers · about 45 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Terms in this lesson

palindrome
A string that reads the same forwards and backwards, such as level or kayak. Comparing characters from both ends towards the middle checks it in one pass.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.