鏈表
鏈表是一種動態的數據結構,當需要插入一個節點的時候,我們只需要爲新創建的節點分配內存空間,將當前節點的next指向新創建的節點,並沒有閒置的內存空間。
例題解答
本文中所有的鏈表的定義如下:
struct ListNode{
int val;
ListNode *next;
ListNode(int v):val(v),next(nullptr){}
};
從尾到頭打印鏈表
題目:輸入一個鏈表的頭節點,從尾到頭反過來打印每個節點的值
- 首先,逆序很容易想到棧這個數據結構,在遍歷鏈表的時候,把每個節點壓入棧中,打印時從棧中彈出來,就能逆着打印
- 其次,可以將值保存在
vector
中,調用reverse()
函數即可逆序打印結果
代碼如下
void printReverseList(ListNode *root){
ListNode *p = root;
stack<ListNode*>nodes;
while(p != nullptr){
nodes.push(p);
p = p ->next;
}
while(!nodes.empty()){
ListNode *t = nodes.top();
printf("%d\t",t->val);
nodes.pop();
}
}
刪除鏈表中的節點
在O(1)的時間內刪除鏈表的某個節點
給定單向鏈表的頭指針和一個節點的指針,定義一個函數在O(1)的時間內刪除該節點
- 通常,刪除鏈表中的節點需要獲取該節點的前一個節點,然而獲取這個前一個節點需要遍歷整個鏈表,時間爲O(n)
- 此方法中可以,將下一個節點複製到該節點中,再刪除下一個節點,即可完成刪除操作。
代碼如下
void deleteNode(ListNode **root,ListNode *del_node){
//刪除的節點不是尾節點
if(del_node->next != nullptr){
ListNode *p_next = del_node->next;
del_node->val = p_next->val;
del_node->next = p_next->next;
delete p_next;
}
//鏈表中只含有唯一一個節點
else if (*root == del_node){
delete del_node;
}
//刪除鏈表中的尾節點
else{
ListNode *p = *root;
while(p->next != del_node){
p = p->next;
}
p->next = nullptr;
delete del_node;
}
}
鏈表中倒數第K個節點
輸入一個鏈表,輸出該鏈表中倒數第K個節點。
- 同樣,常規的方法首先遍歷鏈表,獲取鏈表中節點的個數,計算下一次遍歷時需要計算的次數。時間複雜度爲O(n)
- O(1)的做法爲設置2個指針,第一個指針向前走k-1步後,第二個指針不動。接着讓第一個指針指向末尾時,第二個指針便指向倒數第k個節點
代碼如下:
int findKNode(ListNode *root,int k) {
ListNode *p_node = root;
ListNode *p_sec = root;
for(int i = 0;i < k-1;i++){
p_node = p_node->next;
}
while(p_node->next != nullptr) {
p_node = p_node->next;
p_sec = p_sec->next;
}
return p_sec->val;
}
鏈表中環的入口節點
題目:如果一個鏈表中包含環,如何找出環的入口節點?
- 首先,要判斷鏈表中是否有環,可以設置快、慢兩個指針,如果他們相遇了則說明鏈表中存在環
- 要找到入口節點可以新建一個指針,同時慢指針繼續以一次一步的速度繼續遍歷,相遇節點即爲環的入口節點,證明如下:
假設快慢節點相遇在c點,慢節點在這個過程中移動的距離爲s,鏈表長度爲L,環的長度爲r,則快節點移動距離爲a+n*r,而快節點的步長爲慢節點的2倍:
a+n*r = 2s
—> a+x = s
a+x = n*r
—> a+x = (n-1)*r + r
—>a+x = (n-1)*r + (L-a)
a = (n-1)*r + (L-a-x)
因此節點從h->d與節點從c->d會在d點相遇
因此環形鏈表可以分解成三個問題
1. 給定一個鏈表,判斷鏈表中是否有環
2. 計算環的大小
3. 尋找環的入口
判斷鏈表中是否有環
要點:設置快慢兩個指針,慢指針一次走一步,快指針一次走兩步,當他們相遇時,則說明鏈表中有環
代碼如下;
bool hasCycle(ListNode *head) {
ListNode *slow,*fast;
if(head == nullptr)
return false;
slow = head->next;
if(slow == nullptr)
return false;
fast = slow->next;
if(fast == nullptr)
return false;
while(slow != nullptr && fast != nullptr) {
if(fast == slow)
return true;
slow = slow ->next;
fast = fast->next;
if(fast != nullptr)
fast = fast->next;
else
return false;
}
return false;
}
計算環的大小
在相遇的節點後,慢節點遍歷一圈後回到當前節點,判斷節點相同即可
尋找環的入口節點
需要第一個問題中的相遇的節點作爲參數,代碼如下
ListNode *detectCycle(ListNode *head) {
ListNode *circle_head = hasCycle(head);
if(circle_head == nullptr)
return nullptr;
ListNode *p = head;
while(p != circle_head) {
p = p->next;
circle_head = circle_head->next;
}
return p;
}
反轉鏈表
題目:定義一個函數,輸入一個鏈表的頭節點,反轉該鏈表並輸入反轉後的鏈表的頭結點
爲了防止鏈表的斷裂,需要使用三個指針,表示當前節點、前一個節點、下一個節點。而反轉之後的鏈表的頭節點是反轉之前next爲null的節點。
代碼如下:
ListNode* reverseList(ListNode *root) {
ListNode *p_reverse_head = nullptr;
ListNode *p_cur = root;
ListNode *p_pre = nullptr;
while(p_cur != nullptr) {
ListNode *p_next = p_cur->next;
if(p_next == nullptr)
p_reverse_head = p_cur;
p_cur -> next = p_pre;
p_pre = p_cur;
p_cur = p_next;
}
return p_reverse_head;
}
(LeetCode21) 合併兩個有序鏈表
將兩個有序鏈表合併爲一個新的有序鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。
輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4
- 使用遍歷的辦法,找出2個鏈表中值較小的那個節點鏈接到新的表頭中
- 使用遞歸的辦法
代碼如下:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == nullptr)
return l2;
else if(l2 == nullptr)
return l1;
ListNode *mergeHead = nullptr;
if(l1->val > l2->val) {
mergeHead = l2;
mergeHead->next = mergeTwoLists(l1,l2->next);
}else {
mergeHead = l1;
mergeHead->next = mergeTwoLists(l1->next,l2);
}
return mergeHead;
}
(LeetCode23)合併K個有序鏈表
合併 k 個排序鏈表,返回合併後的排序鏈表
- 可以每次兩兩排序,將排序後的鏈表重新加入到
lists
中去,並在lists
中刪除以及排序過的鏈表
代碼如下:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty())
return nullptr;
while(lists.size() > 1) {
lists.push_back(mergeTwoLists(lists[0],lists[1]));
lists.erase(lists.begin());
lists.erase(lists.begin());
}
return lists.front();
}
複雜鏈表的複製
- 爲了實現O(1)的複雜度,先將鏈表在原鏈表中複製一遍
- 在新的節點上鍊接
siblingNodes
- 斷開鏈接,選擇偶數位置的節點
代碼如下:
struct ListNode{
int val;
ListNode *next;
ListNode *sibling;
ListNode() = default;
ListNode(int v):val(v),next(nullptr),sibling(nullptr){}
};
void copyNodes(ListNode *root) {
ListNode *head = root;
while(head != nullptr) {
ListNode *temp = new ListNode();
temp->val = head->val;
temp->next = head->next;
head->next = temp;
head = temp->next;
}
}
void connectSiblingNodes(ListNode *root) {
ListNode *head = root;
while(head != nullptr) {
ListNode *cloned = head->next;
if(head->sibling != nullptr){
cloned->sibling = head->sibling->next;
}
head = cloned->next;
}
}
ListNode* reconnectNodes(ListNode *root){
ListNode *node = root;
ListNode *cloned_head = nullptr,*cloned_node = nullptr;
if(node != nullptr) {
cloned_head = cloned_node = node->next;
node ->next = cloned_node ->next;
node = node->next;
}
while(node != nullptr) {
cloned_node->next = node ->next;
cloned_node = cloned_node ->next;
node->next = cloned_node ->next;
node = node->next;
}
return cloned_head;
}
ListNode* clone(ListNode *head){
copyNodes(head);
connectSiblingNodes(head);
ListNode *root = reconnectNodes(head);
return root;
}