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])
Difficulty
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
AI Disclaimer
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.
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.
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.
Code Quality
Complexity Analysis
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
Other Solutions
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++ |
Videos
Solved On The Same Day
- problem 1073: Adding Two Negabinary Numbers
- problem 1117: Building H2O
- problem 440: K-th Smallest in Lexicographical Order
Extra
- 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