Design your implementation of the linked list. You can choose to use the singly linked list or the doubly linked list. A node in a singly linked list should have two attributes: val
and next
. val
is the value of the current node, and next
is a pointer/reference to the next node. If you want to use the doubly linked list, you will need one more attribute prev
to indicate the previous node in the linked list. Assume all nodes in the linked list are 0-indexed.
Implement these functions in your linked list class:
- get(index) : Get the value of the
index
-th node in the linked list. If the index is invalid, return-1
. - addAtHead(val) : Add a node of value
val
before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. - addAtTail(val) : Append a node of value
val
to the last element of the linked list. - addAtIndex(index, val) : Add a node of value
val
before theindex
-th node in the linked list. Ifindex
equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. If index is negative, the node will be inserted at the head of the list. - deleteAtIndex(index) : Delete the
index
-th node in the linked list, if the index is valid.
Example:
MyLinkedList linkedList = new MyLinkedList(); linkedList.addAtHead(1); linkedList.addAtTail(3); linkedList.addAtIndex(1, 2); // linked list becomes 1->2->3 linkedList.get(1); // returns 2 linkedList.deleteAtIndex(1); // now the linked list is 1->3 linkedList.get(1); // returns 3
Note:
- All values will be in the range of
[1, 1000]
. - The number of operations will be in the range of
[1, 1000]
. - Please do not use the built-in LinkedList library.
這道題讓我們實現一個鏈表的數據結構,說不能使用現成的鏈表數據結構,需要自己定義結點,說是可以實現雙向或者單向的鏈表。既然有的選,那肯定選簡單的單向鏈表了。首先就是要先自己定義一個結點的數據結構了,好在 LeetCode 中有很多很多的鏈表有關的題目,隨便打開一個,並且參考一下結點的定義即可。然后看需要實現的哪些函數,分別是根據坐標取結點,在鏈表開頭和末尾加結點,根據坐標位置加結點,根據坐標位置刪除結點。既然是結點組成的鏈表,那么肯定不能向數組那樣可以根據坐標直接訪問元素,肯定至少要知道表頭的位置。同時,在根據鏈表取結點函數說明了給定的位置可能是非法的,則也要知道鏈表中所有元素的個數,這樣可以快速的判定給定的位置是否合法。
好,下面來看每個函數如何實現。首先來看根據坐標取結點函數,先判定 index 是否合法,然后從表頭向后移動 index 個位置,找到要返回的結點即可。對於增加表頭函數就比較簡單了,新建一個頭結點,next 連上 head,然后 head 重新指向這個新結點,同時 size 自增1。同樣,對於增加表尾結點函數,首先遍歷到表尾,然后在之后連上一個新建的結點,同時 size 自增1。下面是根據位置來加結點,肯定還是先來判定 index 是否合法,題目要求有過變動,新加一條說是當 index 為負數時,要在表頭加個結點,這樣的話只需要判斷 index 是否大於 size 這一種非法情況。然后再處理一個 corner case,就是當 index 小於等於0的時候,直接調用前面的表頭加結點函數即可。然后就是往后遍歷 index-1 個結點,這里為啥要減1呢,因為要加入結點的話,必須要知道加入位置前面一個結點才行,鏈表加入結點的問題之前的題目中做過很多,這里就不說細節了,最后 size 還是要自增1。根據位置刪除結點也是大同小異,沒有太大的難度,參見代碼如下:
解法一:
class MyLinkedList { public: MyLinkedList() { head = NULL; size = 0; } int get(int index) { if (index < 0 || index >= size) return -1; Node *cur = head; for (int i = 0; i < index; ++i) cur = cur->next; return cur->val; } void addAtHead(int val) { Node *t = new Node(val, head); head = t; ++size; } void addAtTail(int val) { Node *cur = head; while (cur->next) cur = cur->next; cur->next = new Node(val, NULL); ++size; } void addAtIndex(int index, int val) { if (index > size) return; if (index <= 0) {addAtHead(val); return;} Node *cur = head; for (int i = 0; i < index - 1; ++i) cur = cur->next; Node *t = new Node(val, cur->next); cur->next = t; ++size; } void deleteAtIndex(int index) { if (index < 0 || index >= size) return; if (index == 0) { head = head->next; --size; return; } Node *cur = head; for (int i = 0; i < index - 1; ++i) cur = cur->next; cur->next = cur->next->next; --size; } private: struct Node { int val; Node *next; Node(int x, Node* n): val(x), next(n) {} }; Node *head, *tail; int size; };
我們可以對上面的解法做一丟丟的優化,主要在末尾添加結點函數 addAtTail() 上,如果要大量調用這個方法的話,每次都要遍歷到鏈表末尾,很不高效。於是可以同時記錄表頭和表尾的位置,這樣就可以直接訪問末尾結點了,缺點是在其他一些地方如果末尾元素改變了,要更新 tail 指針,否則就會出錯,參見代碼如下:
解法二:
class MyLinkedList { public: MyLinkedList() { head = NULL; tail = NULL; size = 0; } int get(int index) { if (index < 0 || index >= size) return -1; Node *cur = head; for (int i = 0; i < index; ++i) cur = cur->next; return cur->val; } void addAtHead(int val) { Node *t = new Node(val, head); head = t; if (size == 0) tail = t; ++size; } void addAtTail(int val) { Node *t = new Node(val, NULL); if (size == 0) {tail = t; head = t;} tail->next = t; tail = t; ++size; } void addAtIndex(int index, int val) { if (index > size) return; if (index <= 0) {addAtHead(val); return;} if (index == size) {addAtTail(val); return;} Node *cur = head; for (int i = 0; i < index - 1; ++i) cur = cur->next; Node *t = new Node(val, cur->next); cur->next = t; ++size; } void deleteAtIndex(int index) { if (index < 0 || index >= size) return; if (index == 0) { head = head->next; --size; return; } Node *cur = head; for (int i = 0; i < index - 1; ++i) cur = cur->next; cur->next = cur->next->next; if (index == size - 1) tail = cur; --size; } private: struct Node { int val; Node *next; Node(int x, Node* n): val(x), next(n) {} }; Node *head, *tail; int size; };
最后來看一種很簡潔的方法,用到了內置的雙向隊列 deque 這個數據結構,很難說這樣算不算 cheating,但是巨簡潔,大愛之~ 參見代碼如下:
解法三:
class MyLinkedList { public: MyLinkedList() {} int get(int index) { return (index >= 0 && index < data.size()) ? data[index] : -1; } void addAtHead(int val) { data.push_front(val); } void addAtTail(int val) { data.push_back(val); } void addAtIndex(int index, int val) { if (index > (int)data.size()) return; if (index <= 0) { addAtHead(val); return; } data.insert(data.begin() + index, val); } void deleteAtIndex(int index) { if (index < 0 || index >= data.size()) return; data.erase(data.begin() + index); } private: deque<int> data; };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/707
參考資料:
https://leetcode.com/problems/design-linked-list/
https://leetcode.com/problems/design-linked-list/discuss/154116/C%2B%2B-deque
https://leetcode.com/problems/design-linked-list/discuss/145380/C%2B%2B-SOLUTION-24ms
https://leetcode.com/problems/design-linked-list/discuss/150999/C%2B%2B-simple-solution-beats-97.27!