Problem 2296

Abstract / Summary

Design a text editor that supports adding text to the end, deleting text from the end, and retrieving text at any position.

Note

The full problem description, including helpful examples, can be found on LeetCode.

Python 2296.py

class TextEditor:
    # heavily rely on Python's somewhat decent list implementation
    def __init__(self):
        self.data = [] # list of characters
        self.pos  = 0

    def addText(self, text: str) -> None:
        self.data[self.pos : self.pos] = [ c for c in text ]
        self.pos += len(text)

    def deleteText(self, k: int) -> int:
        k = min(k, self.pos) # careful if not enough characters
        self.data[self.pos - k : self.pos] = []
        self.pos -= k
        return k

    def cursorLeft(self, k: int) -> str:
        self.pos -= min(self.pos, k) # careful if not enough characters
        return "".join(self.data[max(self.pos - 10, 0) : self.pos])

    def cursorRight(self, k: int) -> str:
        self.pos = min(len(self.data), self.pos + k)
        return "".join(self.data[max(self.pos - 10, 0) : self.pos])
23 lines | 871 bytes | submitted June 11, 2024 | GitHub   22% comment ratio | tab size 4 | low complexity

Difficulty

This problem is ranked as
easy medium hard

and has an acceptance rate of about 51% (80k+ submissions).

Typical patterns to solve this problem:
  • Design
  • Doubly Linked List
  • Linked List
  • Simulation
  • Stack
  • String
I solved 663 easy, 1250 medium and 309 hard problems

AI Disclaimer

Every single line of code was written by me, 100% from scratch, between August 2023 and August 2024.
However, two years later I set up this website and used various AI tools to generate most of the walkthrough / explanations below.

See this dedicated page for more details.
LeetCode profile | GitHub repo

Code Walkthrough

Overview

The TextEditor class implements a simple mutable text buffer that supports insertion, deletion, and cursor movement. Internally it stores the characters in a Python list named data and tracks the cursor position with the integer pos. All operations manipulate the list directly using slice assignment, which leverages Python’s built-in list handling for efficient in-place modifications.

Core Data Structure

self.data = []   # list of characters
self.pos  = 0    # cursor index, 0-based

The choice of a list of single-character strings provides constant-time element access and allows slice operations to insert or delete contiguous ranges without creating intermediate strings. The cursor index pos always points to the position immediately after the last character that is considered “to the left” of the cursor.

Adding Text

The addText method inserts a new string at the current cursor location and advances the cursor past the inserted characters.

def addText(self, text: str) -> None:
    self.data[self.pos : self.pos] = [c for c in text]
    self.pos += len(text)

The slice self.data[self.pos : self.pos] denotes an empty region at the cursor. By assigning a list comprehension that expands the input string into individual characters, the method inserts the new characters directly into the underlying list. After insertion the cursor is moved forward by the length of the inserted text.

Deleting Text

The deleteText method removes up to k characters that lie to the left of the cursor and returns the actual number of characters removed.

def deleteText(self, k: int) -> int:
    k = min(k, self.pos)               # ensure we do not delete beyond start
    self.data[self.pos - k : self.pos] = []
    self.pos -= k
    return k

The first line caps k at the current cursor position, guaranteeing that the slice indices remain valid even when the requested deletion exceeds the available characters. The slice assignment with an empty list deletes the specified range in place, and the cursor is shifted left by the same amount.

Moving Cursor Left

The cursorLeft method moves the cursor left by at most k positions and returns the last up to ten characters that appear before the new cursor location.

def cursorLeft(self, k: int) -> str:
    self.pos -= min(self.pos, k)       # clamp movement to start of buffer
    return "".join(self.data[max(self.pos - 10, 0) : self.pos])

The subtraction uses min(self.pos, k) to avoid moving the cursor before the beginning of the buffer. The return expression extracts a slice that starts ten characters before the cursor (or at index 0 if fewer than ten characters exist) and concatenates the slice into a string.

Moving Cursor Right

The cursorRight method moves the cursor right by at most k positions, bounded by the end of the buffer, and similarly returns the last up to ten characters before the new cursor location.

def cursorRight(self, k: int) -> str:
    self.pos = min(len(self.data), self.pos + k)   # clamp to buffer end
    return "".join(self.data[max(self.pos - 10, 0) : self.pos])

The assignment to self.pos uses min to ensure the cursor never exceeds the length of the data list. The returned string is constructed in the same way as in cursorLeft.

Edge Cases Handled

When deleting text, the method first reduces k to the smaller of the requested count and the current cursor position. This prevents attempts to delete characters that do not exist, such as calling deleteText on an empty buffer or requesting more characters than are present to the left of the cursor.

Cursor movement methods both clamp the movement distance. In cursorLeft, the cursor is never moved to a negative index because the decrement uses min(self.pos, k). In cursorRight, the cursor is never advanced beyond the length of the data list because the new position is computed with min(len(self.data), self.pos + k). These safeguards guarantee that slice operations later in the methods always receive valid start and end indices.

The string returned by the cursor movement methods always reflects up to ten characters preceding the cursor. The slice start index is calculated with max(self.pos - 10, 0), which gracefully handles situations where fewer than ten characters exist before the cursor, such as when the cursor is near the beginning of the buffer.

Performance Optimizations Observed

The implementation relies heavily on Python’s list slice assignment, which is implemented in C and performs bulk memory moves internally. Inserting characters with self.data[self.pos : self.pos] = [...] avoids the need to shift elements one by one in Python code; the underlying list handles the shift efficiently. Similarly, deleting a range with self.data[self.pos - k : self.pos] = [] removes a contiguous block in a single operation, minimizing the overhead of element removal.

Using a list of single-character strings rather than a single concatenated string avoids the cost of repeatedly rebuilding large immutable strings. Each operation works directly on the mutable list, keeping the cost of insertion and deletion proportional to the number of elements moved rather than to the total size of the buffer.

The cursor movement methods compute the return string by joining a slice of at most ten characters. Because the slice size is bounded, the join operation remains fast regardless of the overall buffer size. This design ensures that frequent cursor queries do not degrade performance as the text grows.

Overall, the code leverages Python’s built-in data structures and slice semantics to achieve concise and efficient text manipulation without resorting to explicit loops or manual memory management.

written by openai gpt

ELI5 - Simplified Explanation

Imagine a magical piece of paper where you can write letters. The computer keeps a long line of these little letter blocks in a specific order, just like beads on a string. There is a tiny invisible finger that points to a spot on this string. This finger shows exactly where the next letter you type will land.

When you use the add command, the machine gently pulls the string apart at that finger's spot and tucks the new letters inside. The finger then slides over to the end of the new letters so it is ready for whatever comes next.

When you use the delete command, the machine looks at where the finger is pointing and grabs the letters right behind it, pulling them off the string like removing blocks from a tower. The finger then moves back to stay safe.

The cursor left and cursor right commands are how the finger moves around. It scoots back and forth along the string. As it moves, it makes sure not to fall off the beginning or the end of the string. Every time the finger stops, it looks at the ten letters right behind it to show you what you have just written.

written by google gemini

Code Quality

Several LLMs ranked my code quality on a scale from 1 (very poor) to 10 (outstanding):
n = 26 | median = 7 | μ = 6.50 | σ = 1.45

Complexity Analysis

Time: linear
As a rule of thumb, the maximum input size for one second execution time is
  • factorial: up to 10 elements
  • exponential: up to 20 elements
  • polynomial: a few thousand elements
  • loglinear: up to 100,000 elements
  • linear: a few million elements
  • logarithmic: a billion elements
  • constant: practically unlimited
generated automatically

Other Solutions

I wrote a basic crawler to find other solutions to that problem.
Obviously, I did that after I solved the problem myself.

In general, AlgoMonster and WalkCCC solutions are high-quality and always worth a look.
I haven't checked most of the other solutions, but they might be helpful as well.

And of course, the LeetCode forum is always a good place (or even the best) to look for alternative approaches.

(click on a column header to sort the table)
author
author solutions
www AlgoMonster C++ Python Java TS
WalkCCC C++ Python Java
leetcode.ca C++ Python Java Go Rust TS
Progiez C++ Python Java
github charles-wangkai Java
everthis JS
fsq C++
JoshCrozier JS
lkwq007 Python
martianoff Scala
wisdompeak C++
AlgoMonster C++, Python, Java, TypeScript
WalkCCC C++, Python, Java
leetcode.ca C++, Go, Java, Python, Rust, TypeScript
Progiez C++, Java, Python
charles-wangkai Java
everthis JavaScript
fsq C++
JoshCrozier JavaScript
lkwq007 Python
martianoff Scala
wisdompeak C++
23 links | 11 authors | 8 languages

Videos

6 videos   did my crawler come up with invalid videos ? drop me a line.

Solved On The Same Day

2 medium 1 hard
4 solutions   June 11, 2024

Extra

There are a few extra pages giving you some insights into my setup:
  • Tech Stack - my development environment and tools
  • AI - how AI helped me create this website (NOT solving the problems)
  • Progress - progression over time
  • Quality - code quality