連結串列基礎
連結串列分為單連結串列、雙連結串列和迴圈連結串列,連結串列在記憶體中與陣列不同,不是連續儲存的。
C++中連結串列的定義方式如下:
// 單連結串列
struct ListNode {
int val; // 節點上儲存的元素
ListNode *next; // 指向下一個節點的指標
ListNode(int x) : val(x), next(NULL) {} // 節點的建構函式
};
這裡引用程式碼隨想錄卡哥總結的陣列和連結串列的區別:
6-203.移除連結串列元素
給你一個連結串列的頭節點head
和一個整數val
,請你刪除連結串列中所有滿足Node.val == val
的節點,並返回新的頭節點。
示例 1:
輸入:head = [1,2,6,3,4,5,6], val = 6
輸出:[1,2,3,4,5]
示例 2:
輸入:head = [], val = 1
輸出:[]
示例 3:
輸入:head = [7,7,7,7], val = 7
輸出:[]
提示:
- 列表中的節點數目在範圍
[0, 104]
內 1 <= Node.val <= 50
0 <= val <= 50
刪除頭節點和非頭節點的操作是不一樣的,這樣會導致程式寫起來較為複雜,首先給出一個使用不同操作來刪除的程式碼:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head != NULL && head->val == val){//刪除頭結點
ListNode* tmp = head;//這裡要臨時建立一個tmp以便於後續釋放空間
head = head -> next;
delete tmp;//釋放記憶體
}
ListNode* cur = head;
while(cur != NULL && cur->next!= NULL){//刪除非頭結點
if(cur->next->val == val){
ListNode* tmp = cur->next;//臨時建立一個tmp以便於後續釋放空間
cur->next = cur->next->next;
delete tmp;//釋放記憶體
}else{
cur = cur->next;
}
}
return head;
}
};
刪除頭節點,首先判斷頭節點是否為空,如果為空則直接返回,當頭節點指向的值為target的時候,我們刪除頭節點,刪除前記得建立一個臨時指標便於後續釋放空間。
然後就可以定義一個cur來指向目前的頭節點,對後續節點進行操作了。
刪除後續節點,依舊判斷是否為空,為空則返回,不為空且下一節點不為空時,判斷指向的值是否為target,是則刪除當前節點,釋放空間cur向後移動一位,不是則直接向後移動一位。最後返回head即可。
使用虛擬頭節點
使用虛擬頭節點的好處是,當我們使用後,就可以使用統一的判斷邏輯來操作後續所有節點,無需像上述程式碼一樣需要預先判斷是否為頭節點。使用虛擬頭節點的程式碼如下所示:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyhead= new ListNode(0);
dummyhead -> next = head;
ListNode* cur = dummyhead;
while(cur != NULL && cur->next!= NULL){//刪除結點
if(cur->next->val == val){
ListNode* tmp = cur->next;//臨時建立一個tmp以便於後續釋放空間
cur->next = cur->next->next;
delete tmp;//釋放記憶體
}else{
cur = cur->next;
}
}
head = dummyhead->next;
delete dummyhead;
return head;
}
};
在使用連結串列時,多畫圖可以幫助理解為什麼有些地方指向next更好,有些地方則是直接指向目標。這裡在最後返回時有一個需要注意的點,就是head可能已經被我們刪除了,那麼此時連結串列的頭其實是dummyhead指向的下一個位置。
7-707.設計連結串列
你可以選擇使用單連結串列或者雙連結串列,設計並實現自己的連結串列。
單連結串列中的節點應該具備兩個屬性:val
和 next
。val
是當前節點的值,next
是指向下一個節點的指標/引用。
如果是雙向連結串列,則還需要屬性 prev
以指示連結串列中的上一個節點。假設連結串列中的所有節點下標從 0 開始。
實現 MyLinkedList
類:
MyLinkedList()
初始化MyLinkedList
物件。int get(int index)
獲取連結串列中下標為index
的節點的值。如果下標無效,則返回-1
。void addAtHead(int val)
將一個值為val
的節點插入到連結串列中第一個元素之前。在插入完成後,新節點會成為連結串列的第一個節點。void addAtTail(int val)
將一個值為val
的節點追加到連結串列中作為連結串列的最後一個元素。void addAtIndex(int index, int val)
將一個值為val
的節點插入到連結串列中下標為index
的節點之前。如果index
等於連結串列的長度,那麼該節點會被追加到連結串列的末尾。如果index
比長度更大,該節點將 不會插入 到連結串列中。void deleteAtIndex(int index)
如果下標有效,則刪除連結串列中下標為index
的節點。
示例:
輸入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
輸出
[null, null, null, null, 2, null, 3]
解釋
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 連結串列變為 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 現在,連結串列變為 1->3
myLinkedList.get(1); // 返回 3
提示:
0 <= index, val <= 1000
- 請不要使用內建的 LinkedList 庫。
- 呼叫
get
、addAtHead
、addAtTail
、addAtIndex
和deleteAtIndex
的次數不超過2000
。
設計連結串列問題,要求對連結串列有較好的掌握,第一次看還是有點暈,以前完全沒學過連結串列這個型別,遇到了相當的困難,不過還是跟著影片寫下來了。這裡就只貼程式碼了,二刷再複習思路。
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}
};
MyLinkedList() {
_dummyHead = new LinkedNode(0);
_size = 0;
}
int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while(index--){ // 如果--index 就會陷入死迴圈
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
// 在連結串列最後面新增一個節點
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newNode;
_size++;
}
// 在第index個節點之前插入一個新節點,例如index為0,那麼新插入的節點為連結串列的新頭節點。
// 如果index 等於連結串列的長度,則說明是新插入的節點為連結串列的尾結點
// 如果index大於連結串列的長度,則返回空
// 如果index小於0,則在頭部插入節點
void addAtIndex(int index, int val) {
if(index > _size) return;
if(index < 0) index = 0;
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--){
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur = _dummyHead;
while(index--){
cur = cur->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
_size--;
}
private:
int _size;
LinkedNode* _dummyHead;
};
8-206.反轉連結串列
這題目明天來補,到時候再編輯