1. 程式人生 > 其它 >雙指標技巧秒殺七道連結串列題目(1)

雙指標技巧秒殺七道連結串列題目(1)

雙指標技巧秒殺七道連結串列題目

對於單鏈表相關的題目,雙指標的運用是非常廣泛的

例如:

1、合併兩個有序連結串列

2、合併 k 個有序連結串列

3、尋找單鏈表的倒數第 k 個節點

4、尋找單鏈表的中點

5、判斷單鏈表是否包含環並找出環起點

6、判斷兩個單鏈表是否相交併找出交點

合併兩個有序連結串列

最基本的連結串列技巧,力扣第 21 題「 合併兩個有序連結串列

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
         // 虛擬頭結點
        ListNode* dummy =  new ListNode(-1);
        ListNode* p=dummy;
        ListNode* p1=list1;
        ListNode* p2=list2;
        while(p1!=nullptr&&p2!=nullptr){
            if(p1->val>p2->val){
                p->next=p2;
                p2=p2->next;
            }else{
                p->next=p1;
                p1=p1->next;
            }
            p=p->next;
        }
        if(p1!=nullptr){
            p->next=p1;
        }
        if(p2!=nullptr){
            p->next=p2;
        }
        return dummy->next;
    }
};

程式碼中還用到一個連結串列的演算法題中是很常見的「虛擬頭結點」技巧,也就是 dummy 節點。如果不使用 dummy 虛擬節點,程式碼會複雜很多,而有了 dummy 節點這個佔位符,可以避免處理空指標的情況,降低程式碼的複雜性。

合併 k 個有序連結串列

我的思路:讓連結串列兩兩合併,類似合併兩個連結串列。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* p1,ListNode* p2){
        if((!p1)||(!p2)) return p1?p1:p2;
        ListNode* dummy=new ListNode();
        ListNode* p=dummy;
        ListNode* p3=p1;
        ListNode* p4=p2;
        while(p3!=nullptr&&p4!=nullptr){
            if(p3->val>p4->val){
                p->next=p4;
                p4=p4->next;
            }else{
                p->next=p3;
                p3=p3->next;
            }
            p=p->next;
        }
        p->next=p3?p3:p4;
        return dummy->next;
    }

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode* p=nullptr;
        for(int i=0;i<lists.size();i++){
            p=mergeTwoLists(p,lists[i]);
        }
        return p;
    }
};

但是這樣的時間複雜度與空間複雜度並不理想,因此可以考慮使用優先順序佇列(二叉堆)這種資料結構,把連結串列節點放入一個最小堆,就可以每次獲得 k 個節點中的最小節點

C++程式碼:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        //lambda
        //因為lambda的型別是由編譯器自動解析的,所以你不需要考慮像引數的型別是函式  指標或函式之類的事情。

        //無論如何,decltype(...)都會解析為可呼叫的物件。
        //lambda 二叉堆每次top放最小的 放進來的是b,
        auto compare=[](ListNode* a,ListNode* b){
            return a->val>=b->val;
        };
        
        //使用優先順序佇列(二叉堆)實現
        priority_queue<ListNode*,vector<ListNode*>,decltype(compare)>pq(compare);
        //虛擬頭節點
        ListNode* dummy=new ListNode(-1);
        ListNode* p=dummy;
        //將k個連結串列的頭節點加入最小堆
        for(ListNode* head:lists){
            if(head!=nullptr){
                pq.push(head);
            }
        }
        while(!pq.empty()){
            //獲得最小節點,接到結果連結串列中
            ListNode* node=pq.top();
            pq.pop();
            p->next=node;
            if(node->next!=nullptr){
                pq.push(node->next);
            }
            //p指標不斷前進
            p=p->next;
        }
        return dummy->next;
    }
};