Losing head/tail while rewiring
Wrong move: Pointer updates overwrite references before they are saved.
Usually fails on: List becomes disconnected mid-operation.
Fix: Store next pointers first and use a dummy head for safer joins.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
Design a text editor with a cursor that can do the following:
When deleting text, only characters to the left of the cursor will be deleted. The cursor will also remain within the actual text and cannot be moved beyond it. More formally, we have that 0 <= cursor.position <= currentText.length always holds.
Implement the TextEditor class:
TextEditor() Initializes the object with empty text.void addText(string text) Appends text to where the cursor is. The cursor ends to the right of text.int deleteText(int k) Deletes k characters to the left of the cursor. Returns the number of characters actually deleted.string cursorLeft(int k) Moves the cursor to the left k times. Returns the last min(10, len) characters to the left of the cursor, where len is the number of characters to the left of the cursor.string cursorRight(int k) Moves the cursor to the right k times. Returns the last min(10, len) characters to the left of the cursor, where len is the number of characters to the left of the cursor.Example 1:
Input
["TextEditor", "addText", "deleteText", "addText", "cursorRight", "cursorLeft", "deleteText", "cursorLeft", "cursorRight"]
[[], ["leetcode"], [4], ["practice"], [3], [8], [10], [2], [6]]
Output
[null, null, 4, null, "etpractice", "leet", 4, "", "practi"]
Explanation
TextEditor textEditor = new TextEditor(); // The current text is "|". (The '|' character represents the cursor)
textEditor.addText("leetcode"); // The current text is "leetcode|".
textEditor.deleteText(4); // return 4
// The current text is "leet|".
// 4 characters were deleted.
textEditor.addText("practice"); // The current text is "leetpractice|".
textEditor.cursorRight(3); // return "etpractice"
// The current text is "leetpractice|".
// The cursor cannot be moved beyond the actual text and thus did not move.
// "etpractice" is the last 10 characters to the left of the cursor.
textEditor.cursorLeft(8); // return "leet"
// The current text is "leet|practice".
// "leet" is the last min(10, 4) = 4 characters to the left of the cursor.
textEditor.deleteText(10); // return 4
// The current text is "|practice".
// Only 4 characters were deleted.
textEditor.cursorLeft(2); // return ""
// The current text is "|practice".
// The cursor cannot be moved beyond the actual text and thus did not move.
// "" is the last min(10, 0) = 0 characters to the left of the cursor.
textEditor.cursorRight(6); // return "practi"
// The current text is "practi|ce".
// "practi" is the last min(10, 6) = 6 characters to the left of the cursor.
Constraints:
1 <= text.length, k <= 40text consists of lowercase English letters.2 * 104 calls in total will be made to addText, deleteText, cursorLeft and cursorRight.Follow-up: Could you find a solution with time complexity of O(k) per call?
Problem summary: Design a text editor with a cursor that can do the following: Add text to where the cursor is. Delete text from where the cursor is (simulating the backspace key). Move the cursor either left or right. When deleting text, only characters to the left of the cursor will be deleted. The cursor will also remain within the actual text and cannot be moved beyond it. More formally, we have that 0 <= cursor.position <= currentText.length always holds. Implement the TextEditor class: TextEditor() Initializes the object with empty text. void addText(string text) Appends text to where the cursor is. The cursor ends to the right of text. int deleteText(int k) Deletes k characters to the left of the cursor. Returns the number of characters actually deleted. string cursorLeft(int k) Moves the cursor to the left k times. Returns the last min(10, len) characters to the left of the cursor, where len is
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Linked List · Stack · Design
["TextEditor","addText","deleteText","addText","cursorRight","cursorLeft","deleteText","cursorLeft","cursorRight"] [[],["leetcode"],[4],["practice"],[3],[8],[10],[2],[6]]
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #2296: Design a Text Editor
class TextEditor {
private StringBuilder left = new StringBuilder();
private StringBuilder right = new StringBuilder();
public TextEditor() {
}
public void addText(String text) {
left.append(text);
}
public int deleteText(int k) {
k = Math.min(k, left.length());
left.setLength(left.length() - k);
return k;
}
public String cursorLeft(int k) {
k = Math.min(k, left.length());
for (int i = 0; i < k; ++i) {
right.append(left.charAt(left.length() - 1));
left.deleteCharAt(left.length() - 1);
}
return left.substring(Math.max(left.length() - 10, 0));
}
public String cursorRight(int k) {
k = Math.min(k, right.length());
for (int i = 0; i < k; ++i) {
left.append(right.charAt(right.length() - 1));
right.deleteCharAt(right.length() - 1);
}
return left.substring(Math.max(left.length() - 10, 0));
}
}
/**
* Your TextEditor object will be instantiated and called as such:
* TextEditor obj = new TextEditor();
* obj.addText(text);
* int param_2 = obj.deleteText(k);
* String param_3 = obj.cursorLeft(k);
* String param_4 = obj.cursorRight(k);
*/
// Accepted solution for LeetCode #2296: Design a Text Editor
type TextEditor struct {
left, right []byte
}
func Constructor() TextEditor {
return TextEditor{}
}
func (this *TextEditor) AddText(text string) {
this.left = append(this.left, text...)
}
func (this *TextEditor) DeleteText(k int) int {
k = min(k, len(this.left))
if k < len(this.left) {
this.left = this.left[:len(this.left)-k]
} else {
this.left = []byte{}
}
return k
}
func (this *TextEditor) CursorLeft(k int) string {
k = min(k, len(this.left))
for ; k > 0; k-- {
this.right = append(this.right, this.left[len(this.left)-1])
this.left = this.left[:len(this.left)-1]
}
return string(this.left[max(len(this.left)-10, 0):])
}
func (this *TextEditor) CursorRight(k int) string {
k = min(k, len(this.right))
for ; k > 0; k-- {
this.left = append(this.left, this.right[len(this.right)-1])
this.right = this.right[:len(this.right)-1]
}
return string(this.left[max(len(this.left)-10, 0):])
}
/**
* Your TextEditor object will be instantiated and called as such:
* obj := Constructor();
* obj.AddText(text);
* param_2 := obj.DeleteText(k);
* param_3 := obj.CursorLeft(k);
* param_4 := obj.CursorRight(k);
*/
# Accepted solution for LeetCode #2296: Design a Text Editor
class TextEditor:
def __init__(self):
self.left = []
self.right = []
def addText(self, text: str) -> None:
self.left.extend(list(text))
def deleteText(self, k: int) -> int:
k = min(k, len(self.left))
for _ in range(k):
self.left.pop()
return k
def cursorLeft(self, k: int) -> str:
k = min(k, len(self.left))
for _ in range(k):
self.right.append(self.left.pop())
return ''.join(self.left[-10:])
def cursorRight(self, k: int) -> str:
k = min(k, len(self.right))
for _ in range(k):
self.left.append(self.right.pop())
return ''.join(self.left[-10:])
# Your TextEditor object will be instantiated and called as such:
# obj = TextEditor()
# obj.addText(text)
# param_2 = obj.deleteText(k)
# param_3 = obj.cursorLeft(k)
# param_4 = obj.cursorRight(k)
// Accepted solution for LeetCode #2296: Design a Text Editor
struct TextEditor {
left: String,
right: String,
}
impl TextEditor {
fn new() -> Self {
TextEditor {
left: String::new(),
right: String::new(),
}
}
fn add_text(&mut self, text: String) {
self.left.push_str(&text);
}
fn delete_text(&mut self, k: i32) -> i32 {
let k = k.min(self.left.len() as i32) as usize;
self.left.truncate(self.left.len() - k);
k as i32
}
fn cursor_left(&mut self, k: i32) -> String {
let k = k.min(self.left.len() as i32) as usize;
for _ in 0..k {
if let Some(c) = self.left.pop() {
self.right.push(c);
}
}
self.get_last_10_chars()
}
fn cursor_right(&mut self, k: i32) -> String {
let k = k.min(self.right.len() as i32) as usize;
for _ in 0..k {
if let Some(c) = self.right.pop() {
self.left.push(c);
}
}
self.get_last_10_chars()
}
fn get_last_10_chars(&self) -> String {
let len = self.left.len();
self.left[len.saturating_sub(10)..].to_string()
}
}
// Accepted solution for LeetCode #2296: Design a Text Editor
class TextEditor {
private left: string[];
private right: string[];
constructor() {
this.left = [];
this.right = [];
}
addText(text: string): void {
this.left.push(...text);
}
deleteText(k: number): number {
k = Math.min(k, this.left.length);
for (let i = 0; i < k; i++) {
this.left.pop();
}
return k;
}
cursorLeft(k: number): string {
k = Math.min(k, this.left.length);
for (let i = 0; i < k; i++) {
this.right.push(this.left.pop()!);
}
return this.left.slice(-10).join('');
}
cursorRight(k: number): string {
k = Math.min(k, this.right.length);
for (let i = 0; i < k; i++) {
this.left.push(this.right.pop()!);
}
return this.left.slice(-10).join('');
}
}
/**
* Your TextEditor object will be instantiated and called as such:
* var obj = new TextEditor()
* obj.addText(text)
* var param_2 = obj.deleteText(k)
* var param_3 = obj.cursorLeft(k)
* var param_4 = obj.cursorRight(k)
*/
Use this to step through a reusable interview workflow for this problem.
Copy all n nodes into an array (O(n) time and space), then use array indexing for random access. Operations like reversal or middle-finding become trivial with indices, but the O(n) extra space defeats the purpose of using a linked list.
Most linked list operations traverse the list once (O(n)) and re-wire pointers in-place (O(1) extra space). The brute force often copies nodes to an array to enable random access, costing O(n) space. In-place pointer manipulation eliminates that.
Review these before coding to avoid predictable interview regressions.
Wrong move: Pointer updates overwrite references before they are saved.
Usually fails on: List becomes disconnected mid-operation.
Fix: Store next pointers first and use a dummy head for safer joins.
Wrong move: Pushing without popping stale elements invalidates next-greater/next-smaller logic.
Usually fails on: Indices point to blocked elements and outputs shift.
Fix: Pop while invariant is violated before pushing current element.