玩轉演算法面試:(五)LeetCode連結串列類問題
在連結串列中穿針引線
連結串列和陣列都是線性結構,但是連結串列和陣列的不同在於陣列可以隨機的對於資料進行訪問。給出索引。可以以O(1)的時間複雜度迅速訪問到該元素。
連結串列只能從頭指標開始。
next指標指向哪裡?
206. Reverse Linked List
反轉一個連結串列
連結串列反轉
不能改變連結串列值。操作每個節點的next指標。
5的指標指向空的。變成指向4的。
next改變示意圖
把1指向NUll之前要儲存next指標
新增pre指標儲存上一個節點
// 時間複雜度: O(n) // 空間複雜度: O(1) class Solution { public: ListNode* reverseList(ListNode* head) { ListNode* pre = NULL; ListNode* cur = head; while( cur != NULL ){ ListNode* next = cur->next; cur->next = pre; pre = cur; cur = next; } return pre; } };
92. Reverse Linked List II
反轉一個連結串列從m到n的元素。
- 如對於連結串列 1->2->3->4->5->NULL, m = 2 , n = 4
- 則返回連結串列 1->4->3->2->5->NULL
- m和n超過連結串列範圍怎麼辦?
- m > n 怎麼辦?
翻轉m到n的元素。
測試連結串列程式。
寫根據陣列建立連結串列和列印連結串列兩個函式
/// LinkedList Test Helper Functions ListNode* createLinkedList(int arr[], int n){ if( n == 0 ) return NULL; ListNode* head = new ListNode(arr[0]); ListNode* curNode = head; for( int i = 1 ; i < n ; i ++ ){ curNode->next = new ListNode(arr[i]); curNode = curNode->next; } return head; } void printLinkedList(ListNode* head){ ListNode* curNode = head; while( curNode != NULL ){ cout << curNode->val << " -> "; curNode = curNode->next; } cout<<"NULL"<<endl; return; } void deleteLinkedList(ListNode* head){ ListNode* curNode = head; while( curNode != NULL ){ ListNode* delNode = curNode; curNode = curNode->next; delete delNode; } return; }
main.cpp:
int main(){
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr)/sizeof(int);
ListNode* head = createLinkedList(arr, n);
printLinkedList(head);
ListNode* head2 = Solution().reverseList(head);
printLinkedList(head2);
deleteLinkedList(head2);
return 0;
}
執行結果
83. Remove Duplicates from Sorted List
給出一個有序連結串列,刪除其中所有重複元素,使得每個元素只保留一次。
- 如 1->1->2,返回1->2
- 如 1->1->2->3->3,返回1->2->3
86. Partition List
給出一個連結串列以及一個數x,將連結串列重新整理,使得小於x的元素在前;大於等於x的元素在後。
- 如 1->4->3->2->5->2,x=3
- 返回 1->2->2->4->3->5
328. Odd Even Linked List
給出一個連結串列,將連結串列重新整理,使得所有索引為奇數的節點排在索引為偶數的節點前面。
- 如 1->2->3->4->5->NULL
- 返回 1->3->5->2->4->NULL
- 第一個節點的索引為1
- 奇數索引的節點和偶數索引的節點在重新整理後要保持相對順序。
2. Add Two Numbers
給出兩個非空連結串列,表示兩個非負整數。其中每一個整數的各位數字以逆序儲存,返回這兩個整數相加所代表的連結串列。
- 如 342 + 465 = 807
- 則給出 2->4->3 和 5->6->4,返回7->0->8
數字中是否有前置的0。(除0以外,沒有前置0)
負數?
445. Add Two Numbers II
給出兩個非空連結串列,表示兩個非負整數。其中每一個整數的各位數字以順序儲存,返回這兩個整數相加所代表的連結串列。
- 如 342 + 465 = 807
- 則給出 3->4->2 和 4->6->5,返回8->0->7
如果不允許修改輸入的連結串列呢?
使用輔助資料結構
設立連結串列的虛擬頭結點
203. Remove Linked List Elements
在連結串列中刪除值為val的所有節點
- 如 1->2->6->3->4->5->6->NULL,要求刪除值為6的節點
- 返回 1->2->3->4->5->NULL
刪除節點
該邏輯對刪除最後一個元素依然適用
該邏輯對刪除第一個元素不適用
// 不使用虛擬頭結點
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while( head != NULL && head->val == val ){
ListNode* node = head;
head = head->next;
delete node;
}
if( head == NULL )
return head;
ListNode* cur = head;
while( cur->next != NULL ){
if( cur->next->val == val ){
ListNode* delNode = cur->next;
cur->next = delNode->next;
delete delNode;
//delNode -> next = NULL;
}
else
cur = cur->next;
}
return head;
}
};
虛擬頭結點
具體實現:
// 使用虛擬頭結點
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while( cur->next != NULL ){
if( cur->next->val == val ){
ListNode* delNode = cur->next;
cur->next = delNode->next;
delete delNode;
}
else
cur = cur->next;
}
ListNode* retNode = dummyHead->next;
delete dummyHead;
return retNode;
}
};
int main() {
int arr[] = {1, 2, 6, 3, 4, 5, 6};
int n = sizeof(arr)/sizeof(int);
ListNode* head = createLinkedList(arr, n);
printLinkedList(head);
Solution().removeElements( head, 6);
printLinkedList(head);
deleteLinkedList( head );
return 0;
}
82. Remove Duplicates from Sorted List II
給定一個有序連結串列,將其中有重複的元素全部刪除。
- 如1->2->3->3->4->4->5,返回1->2->5
- 如1->1->1->2->3,返回2->3
21. Merge Two Sorted Lists
merge兩個有序的連結串列
複雜的連結串列操作
24. Swap Nodes in Pairs
給定一個連結串列,對於每兩個相鄰的節點,交換其位置。
- 如:連結串列為 1->2->3->4->NULL
- 返回:2->1->4->3->NULL
- 只能對節點進行操作,不能修改節點的值
虛擬頭結點
虛擬頭結點
next指標調整
核心在於建立幾個指標預先保留。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* p = dummyHead;
while( p->next && p->next->next ){
ListNode* node1 = p->next;
ListNode* node2 = node1->next;
ListNode* next = node2->next;
node2->next = node1;
node1->next = next;
p->next = node2;
p = node1;
}
ListNode* retHead = dummyHead->next;
delete dummyHead;
return retHead;
}
};
int main() {
int arr[] = {1, 2, 3, 4};
int n = sizeof(arr)/sizeof(int);
ListNode* head = createLinkedList(arr, n);
printLinkedList(head);
head = Solution().swapPairs( head );
printLinkedList(head);
deleteLinkedList(head);
return 0;
}
思考:可不可以不用next指標?
25. Reverse Nodes in k-Group
給定一個連結串列,每k個節點為一組,反轉每一組的k個節點。k為正整數且小於等於連結串列長度。如果連結串列長度不是k的整數倍,剩餘部分不需要進行反轉。如: 1->2->3->4->5->NULL
- 若 k = 2,則結果為:2->1->4->3->5->NULL
- 若 k = 3,則結果為:3->2->1->4->5->NULL
147. Insertion Sort List
為一個連結串列進行插入排序
148. Sort List
寫一個排序演算法,用O(nlogn)的時間複雜度為一個連結串列進行排序
歸併排序不需要使用陣列索引:自底向上。
不僅僅是穿針引線
237. Delete Node in a Linked List
給定連結串列中的一個節點,刪除該節點
class Solution {
public:
void deleteNode(ListNode* node) {
}
};
3賦值上4,然後把4的next儲存。讓第一個4指向第二個四的next
3賦值上4,然後把4的next儲存。讓第一個4指向第二個四的next
class Solution {
public:
void deleteNode(ListNode* node) {
assert(node != NULL && node->next != NULL);
node->val = node->next->val;
ListNode* delNode = node->next;
node->next = delNode->next;
delete delNode;
return;
}
};
特殊情況改變節點的值來實現我們需要的功能。
雙指標技術
19. Remove Nth Node From End of List
給定一個連結串列,刪除倒數第n個節點
- 如:1->2->3->4->5->NULL, n = 2
- 返回:1->2->3->5
n從0計還是從1計
n不合法,負數或者大於連結串列長度如何處理(保證n合法)
解法1:先遍歷一遍計算連結串列長度;再遍歷一遍刪除倒數第n個節點
遍歷兩遍連結串列。能否只遍歷一遍連結串列?
pq長度固定
具體實現:
// 先記錄連結串列總長度
// 需要對連結串列進行兩邊遍歷
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
int length = 0;
for(ListNode* cur = dummyHead->next ; cur != NULL ; cur = cur->next )
length ++;
//cout<<length<<endl;
int k = length - n;
assert( k >= 0 );
ListNode* cur = dummyHead;
for( int i = 0 ; i < k ; i ++ )
cur = cur -> next;
ListNode* delNode = cur->next;
cur->next = delNode->next;
delete delNode;
ListNode* retNode = dummyHead->next;
delete dummyHead;
return retNode;
}
};
使用雙指標:
// 使用雙指標, 對連結串列只遍歷了一遍
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* p = dummyHead;
ListNode* q = dummyHead;
for( int i = 0 ; i < n + 1 ; i ++ ){
assert(q);
q = q->next;
}
while( q ){
p = p->next;
q = q->next;
}
ListNode* delNode = p->next;
p->next = delNode->next;
delete delNode;
ListNode* retNode = dummyHead->next;
delete dummyHead;
return retNode;
}
};
61. Rotate List
給定一個連結串列,讓這個連結串列向右旋轉k位。其中k為非負數。
- 如:1->2->3->4->5->NULL, k = 2
- 第一次旋轉:5->1->2->3->4->NULL
- 第二次旋轉:4->5->1->2->3->NULL
143. Reorder List
給定一個連結串列 L(0) -> L(1) -> L(2) -> … -> L(n-1) -> L(n)
將其變為 L(0) -> L(n) -> L(1) -> L(n-1) -> L(2) -> L(n-2)…
的形式
- 連結串列無法隨機訪問資料,如何獲得中間的元素?
- 兩次遍歷?一次遍歷?
234. Palindrome Linked List
給一個連結串列,判斷這個連結串列是否為迴文(正看反看)連結串列。
- 能否使用O(1)的空間複雜度解決問題?
作者:天涯明月笙
連結:https://www.jianshu.com/p/98aac3b8c12a