请你设计一个带光标的文本编辑器,它可以实现以下功能:
添加: 在光标所在处添加文本。
删除: 在光标所在处删除文本(模拟键盘的删除键)。
移动: 将光标往左或者往右移动。
当删除文本时,只有光标左边的字符会被删除。光标会留在文本内,也就是说任意时候 0 <= cursor.position <= currentText.length
都成立。
请你实现 TextEditor
类:
TextEditor()
用空文本初始化对象。
void addText(string text)
将 text
添加到光标所在位置。添加完后光标在 text
的右边。
int deleteText(int k)
删除光标左边 k
个字符。返回实际删除的字符数目。
string cursorLeft(int k)
将光标向左移动 k
次。返回移动后光标左边 min(10, len)
个字符,其中 len
是光标左边的字符数目。
string cursorRight(int k)
将光标向右移动 k
次。返回移动后光标左边 min(10, len)
个字符,其中 len
是光标左边的字符数目。
示例 1:
**输入:**
["TextEditor", "addText", "deleteText", "addText", "cursorRight", "cursorLeft", "deleteText", "cursorLeft", "cursorRight"]
[[], ["leetcode"], [4], ["practice"], [3], [8], [10], [2], [6]]
**输出:**
[null, null, 4, null, "etpractice", "leet", 4, "", "practi"]
**解释:**
TextEditor textEditor = new TextEditor(); // 当前 text 为 "|" 。('|' 字符表示光标)
textEditor.addText("leetcode"); // 当前文本为 "leetcode|" 。
textEditor.deleteText(4); // 返回 4
// 当前文本为 "leet|" 。
// 删除了 4 个字符。
textEditor.addText("practice"); // 当前文本为 "leetpractice|" 。
textEditor.cursorRight(3); // 返回 "etpractice"
// 当前文本为 "leetpractice|".
// 光标无法移动到文本以外,所以无法移动。
// "etpractice" 是光标左边的 10 个字符。
textEditor.cursorLeft(8); // 返回 "leet"
// 当前文本为 "leet|practice" 。
// "leet" 是光标左边的 min(10, 4) = 4 个字符。
textEditor.deleteText(10); // 返回 4
// 当前文本为 "|practice" 。
// 只有 4 个字符被删除了。
textEditor.cursorLeft(2); // 返回 ""
// 当前文本为 "|practice" 。
// 光标无法移动到文本以外,所以无法移动。
// "" 是光标左边的 min(10, 0) = 0 个字符。
textEditor.cursorRight(6); // 返回 "practi"
// 当前文本为 "practi|ce" 。
// "practi" 是光标左边的 min(10, 6) = 6 个字符。
提示:
1 <= text.length, k <= 40
text
只含有小写英文字母。
调用 addText
,deleteText
,cursorLeft
和 cursorRight
的 总 次数不超过 2 * 104
次。
进阶: 你能设计并实现一个每次调用时间复杂度为 O(k)
的解决方案吗?
本题 视频讲解 已出炉,欢迎点赞三连~
方法一:双向链表 提示 1 注意添加、删除和移动操作均在光标附近完成,且 text 的长度和 k 都很小。
提示 2 用链表模拟所有操作,每个节点存储一个字符。
光标指向链表中的一个节点(该节点保存着光标左边的字符)。
提示 3 用一个哨兵节点表示光标位于文本最左侧的情况,此时光标左侧无字符,即指向哨兵节点。
复杂度分析 时空复杂度均与输入量成正比(线性)。
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class Node : __slots__ = 'prev' , 'next' , 'ch' def __init__ (self, ch='' ): self.prev = None self.next = None self.ch = ch def insert (self, node: 'Node' ) -> 'Node' : node.prev = self node.next = self.next node.prev.next = node node.next .prev = node return node def remove (self ) -> None : self.prev.next = self.next self.next .prev = self.prev class TextEditor : def __init__ (self ): self.root = self.cur = Node() self.root.prev = self.root self.root.next = self.root def addText (self, text: str ) -> None : for ch in text: self.cur = self.cur.insert(Node(ch)) def deleteText (self, k: int ) -> int : k0 = k while k and self.cur != self.root: self.cur = self.cur.prev self.cur.next .remove() k -= 1 return k0 - k def text (self ) -> str : s = [] k, cur = 10 , self.cur while k and cur != self.root: s.append(cur.ch) cur = cur.prev k -= 1 return '' .join(reversed (s)) def cursorLeft (self, k: int ) -> str : while k and self.cur != self.root: self.cur = self.cur.prev k -= 1 return self.text() def cursorRight (self, k: int ) -> str : while k and self.cur.next != self.root: self.cur = self.cur.next k -= 1 return self.text()
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 class TextEditor { Node root, cur; public TextEditor () { root = cur = new Node (); root.prev = root; root.next = root; } public void addText (String text) { for (var i = 0 ; i < text.length(); i++) cur = cur.insert(new Node (text.charAt(i))); } public int deleteText (int k) { var k0 = k; for (; k > 0 && cur != root; --k) { cur = cur.prev; cur.next.remove(); } return k0 - k; } String text () { var s = new StringBuilder (); var cur = this .cur; for (var k = 10 ; k > 0 && cur != root; --k) { s.append(cur.ch); cur = cur.prev; } return s.reverse().toString(); } public String cursorLeft (int k) { for (; k > 0 && cur != root; --k) cur = cur.prev; return text(); } public String cursorRight (int k) { for (; k > 0 && cur.next != root; --k) cur = cur.next; return text(); } } class Node { Node prev, next; char ch; Node() {} Node(char ch) { this .ch = ch; } Node insert (Node node) { node.prev = this ; node.next = this .next; node.prev.next = node; node.next.prev = node; return node; } void remove () { this .prev.next = this .next; this .next.prev = this .prev; } }
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class TextEditor { list<char > l; list<char >::iterator cur = l.begin (); public : void addText (string text) { for (char ch : text) l.insert (cur, ch); } int deleteText (int k) { int k0 = k; for (; k && cur != l.begin (); --k) cur = l.erase (prev (cur)); return k0 - k; } string text () { string s; auto it = cur; for (int k = 10 ; k && it != l.begin (); --k) s += *--it; reverse (s.begin (), s.end ()); return s; } string cursorLeft (int k) { for (; k && cur != l.begin (); --k) --cur; return text (); } string cursorRight (int k) { for (; k && cur != l.end (); --k) ++cur; return text (); } };
[sol1-Go] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 type TextEditor struct { *list.List cur *list.Element } func Constructor () TextEditor { l := list.New() return TextEditor{l, l.PushBack(nil )} } func (l *TextEditor) AddText(text string ) { for _, ch := range text { l.cur = l.InsertAfter(byte (ch), l.cur) } } func (l *TextEditor) DeleteText(k int ) int { k0 := k for ; k > 0 && l.cur.Value != nil ; k-- { pre := l.cur.Prev() l.Remove(l.cur) l.cur = pre } return k0 - k } func (l *TextEditor) text() string { s := []byte {} for k, cur := 10 , l.cur; k > 0 && cur.Value != nil ; k-- { s = append (s, cur.Value.(byte )) cur = cur.Prev() } for i, n := 0 , len (s); i < n/2 ; i++ { s[i], s[n-1 -i] = s[n-1 -i], s[i] } return string (s) } func (l *TextEditor) CursorLeft(k int ) string { for ; k > 0 && l.cur.Value != nil ; k-- { l.cur = l.cur.Prev() } return l.text() } func (l *TextEditor) CursorRight(k int ) string { for ; k > 0 && l.cur.Next() != nil ; k-- { l.cur = l.cur.Next() } return l.text() }
方法二:对顶栈 用两个栈头对头,光标的左右移动就相当于两个栈来回倒;对于插入和删除操作,就相当于在左边那个栈上入栈出栈。
复杂度分析 时空复杂度均与输入量成正比(线性)。
相似题目
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 : k0 = k while k and self.left: self.left.pop() k -= 1 return k0 - k def text (self ) -> str : return '' .join(self.left[-10 :]) def cursorLeft (self, k: int ) -> str : while k and self.left: self.right.append(self.left.pop()) k -= 1 return self.text() def cursorRight (self, k: int ) -> str : while k and self.right: self.left.append(self.right.pop()) k -= 1 return self.text()
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class TextEditor { StringBuilder left = new StringBuilder (), right = new StringBuilder (); 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; } String text () { return left.substring(Math.max(left.length() - 10 , 0 )); } public String cursorLeft (int k) { for (; k > 0 && !left.isEmpty(); --k) { right.append(left.charAt(left.length() - 1 )); left.deleteCharAt(left.length() - 1 ); } return text(); } public String cursorRight (int k) { for (; k > 0 && !right.isEmpty(); --k) { left.append(right.charAt(right.length() - 1 )); right.deleteCharAt(right.length() - 1 ); } return text(); } }
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class TextEditor { string left, right; public : void addText (string text) { left += text; } int deleteText (int k) { k = min (k, (int ) left.length ()); left.resize (left.length () - k); return k; } string text () { return left.substr (max ((int ) left.size () - 10 , 0 )); } string cursorLeft (int k) { for (; k && !left.empty (); --k) { right += left.back (); left.pop_back (); } return text (); } string cursorRight (int k) { for (; k && !right.empty (); --k) { left += right.back (); right.pop_back (); } return text (); } };
[sol2-Go] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 type TextEditor struct { l, r []byte }func Constructor () TextEditor { return TextEditor{} }func (t *TextEditor) AddText(text string ) { t.l = append (t.l, text...) } func (t *TextEditor) DeleteText(k int ) int { if k < len (t.l) { t.l = t.l[:len (t.l)-k] } else { k = len (t.l) t.l = t.l[:0 ] } return k } func (t *TextEditor) text() string { return string (t.l[max(len (t.l)-10 , 0 ):]) } func (t *TextEditor) CursorLeft(k int ) string { for ; k > 0 && len (t.l) > 0 ; k-- { t.r = append (t.r, t.l[len (t.l)-1 ]) t.l = t.l[:len (t.l)-1 ] } return t.text() } func (t *TextEditor) CursorRight(k int ) string { for ; k > 0 && len (t.r) > 0 ; k-- { t.l = append (t.l, t.r[len (t.r)-1 ]) t.r = t.r[:len (t.r)-1 ] } return t.text() } func max (a, b int ) int { if b > a { return b }; return a }
方法三:Splay(超纲) 本题还可以用 Splay 来模拟文本的添加和删除,由于该算法超纲,感兴趣的同学可以查阅相关资料。具体做法在本题的 视频讲解 中有说明。完整的 Splay 模板见我的 算法竞赛模板库 。
[sol3-Go] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 type node struct { ch [2 ]*node sz int key byte } func (o *node) cmpKth(k int ) int { d := k - o.ch[0 ].size() - 1 switch { case d < 0 : return 0 case d > 0 : return 1 default : return -1 } } func (o *node) size() int { if o != nil { return o.sz } return 0 } func (o *node) maintain() { o.sz = 1 + o.ch[0 ].size() + o.ch[1 ].size() } func buildSplay (s string ) *node { if s == "" { return nil } m := len (s) / 2 o := &node{key: s[m]} o.ch[0 ] = buildSplay(s[:m]) o.ch[1 ] = buildSplay(s[m+1 :]) o.maintain() return o } func (o *node) rotate(d int ) *node { x := o.ch[d^1 ] o.ch[d^1 ] = x.ch[d] x.ch[d] = o o.maintain() x.maintain() return x } func (o *node) splay(k int ) (kth *node) { d := o.cmpKth(k) if d < 0 { return o } k -= d * (o.ch[0 ].size() + 1 ) c := o.ch[d] if d2 := c.cmpKth(k); d2 >= 0 { c.ch[d2] = c.ch[d2].splay(k - d2*(c.ch[0 ].size()+1 )) if d2 == d { o = o.rotate(d ^ 1 ) } else { o.ch[d] = c.rotate(d) } } return o.rotate(d ^ 1 ) } func (o *node) split(k int ) (lo, ro *node) { lo = o.splay(k) ro = lo.ch[1 ] lo.ch[1 ] = nil lo.maintain() return } func (o *node) merge(ro *node) *node { o = o.splay(o.size()) o.ch[1 ] = ro o.maintain() return o } type TextEditor struct { root *node cur int } func Constructor () TextEditor { return TextEditor{} }func (t *TextEditor) AddText(text string ) { if t.cur == 0 { t.root = buildSplay(text).merge(t.root) } else { lo, ro := t.root.split(t.cur) t.root = lo.merge(buildSplay(text)).merge(ro) } t.cur += len (text) } func (t *TextEditor) DeleteText(k int ) int { if t.cur == 0 { return 0 } if t.cur <= k { _, t.root = t.root.split(t.cur) ans := t.cur t.cur = 0 return ans } else { lo, ro := t.root.split(t.cur) t.cur -= k lo, _ = lo.split(t.cur) t.root = lo.merge(ro) return k } } func (t *TextEditor) text() string { if t.cur == 0 { return "" } k := max(t.cur-10 , 0 ) t.root = t.root.splay(k + 1 ) ans := make ([]byte , 1 , t.cur-k) ans[0 ] = t.root.key var inorder func (*node) bool inorder = func (o *node) bool { if o == nil { return false } if inorder(o.ch[0 ]) || len (ans) == cap (ans) { return true } ans = append (ans, o.key) return inorder(o.ch[1 ]) } inorder(t.root.ch[1 ]) return string (ans) } func (t *TextEditor) CursorLeft(k int ) string { t.cur = max(t.cur-k, 0 ) return t.text() } func (t *TextEditor) CursorRight(k int ) string { t.cur = min(t.cur+k, t.root.size()) return t.text() } func min (a, b int ) int { if a > b { return b }; return a }func max (a, b int ) int { if a < b { return b }; return a }