LeetCode-鏈表

LeetCode-鏈表

鏈表(Linked List)是一種常見的基礎數(shù)據(jù)結構,是一種線性表,但是并不會按線性的順序存儲數(shù)據(jù),而是在每一個節(jié)點里存到下一個節(jié)點的指針(Pointer)。

image-20191105225333488

由于不必須按順序存儲,鏈表在插入的時候可以達到 O(1)O(1) 的復雜度,比另一種線性表 —— 順序表快得多,但是查找一個節(jié)點或者訪問特定編號的節(jié)點則需要 O(n)O(n) 的時間,而順序表相應的時間復雜度分別是 O(log\ n)O(log n) 和 O(1)O(1)。

對于鏈表相關的問題,應該注意以下幾點:

  • 頭節(jié)點的使用,即dummyNode的使用,可以減少邊界條件判斷。涉及到頭節(jié)點的操作,比如可能刪除head等等,使用dummyNode可以減少判斷。最后返回dummyNode.next

  • 創(chuàng)建新的節(jié)點時,注意在棧上分配對象,如果使用new 需要delete。ps:后續(xù)一些題解沒有及時更正該方面的問題。

  • 快慢指針。多用于尋找中間節(jié)點等,衍生問題環(huán)形鏈表等。主要在于在O(N)的時間來快速獲取到想要的節(jié)點。

    使用快慢指針查找中間節(jié)點時,當fast和slow都指向head和slow指向head,fast指向head->next 獲取到的中間節(jié)點及邊界條件不一致,需要注意。

  • cut函數(shù),cut(ListNode* node,int k) 將鏈表前k個值切割,返回第k+1個node;多用于分隔鏈表。

  • 反轉鏈表,需要pre=null,cur=head,next 三個指針, 每次進行更新,最后返回pre。

  • 合并鏈表,注意dummyNode的使用以及邊界判空。

  • 刪除節(jié)點,刪除當前節(jié)點,可以將下個節(jié)點的值復制給當前節(jié)點,并指向下下個節(jié)點。另一種思路時需要找到刪除節(jié)點的前驅。

  • 時間復雜度,可以考慮一些輔助工作,如map,stack,set等。

  • 另一個就是鏈表和排序算法以及樹的遍歷的結合。歸并排序,dfs等等??梢缘群罄m(xù)章節(jié)進行分析。

  • 鏈表相關的問題也可以考慮遞歸的方式進行解決。從而簡化問題。

下面介紹LeetCode常見的鏈表問題。

1.反轉鏈表

206.反轉鏈表。

示例:

輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head==NULL || head->next==NULL){
            return head;
        }
        ListNode* pre=NULL;
        ListNode* cur=head;
        ListNode* next;
        while(cur){
            next=cur->next;
            cur->next=pre;
            pre=cur;
            cur=next;
        }
        return pre;
    }
};

92.反轉鏈表II

反轉從位置 mn 的鏈表。請使用一趟掃描完成反轉。

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        if (head==NULL || head->next==NULL){
            return head;
        }
        ListNode* pre=NULL;
        ListNode* cur=head;
        ListNode* next;
        while(m>1){
            pre=cur;
            cur=cur->next;
            m--;
            n--;
        }
        // 需要記錄下pre 和 tail;即反轉后的頭節(jié)點和尾節(jié)點
        ListNode* con = pre; //反轉后的頭節(jié)點
        ListNode* tail=cur; // 反轉后的尾節(jié)點
        while(n>0){
            next=cur->next;
            cur->next=pre;
            pre=cur;
            cur=next;
            n--;
        }
        if(con){
            con->next=pre; //進行連接            
        }else{
            head=pre; //此時證明從第一個節(jié)點開始反轉,那么頭節(jié)點直接就是反轉后的頭節(jié)點
        }
        tail->next=cur; //尾節(jié)點的next設置為下一個節(jié)點cur
        return head;
    }
};

2.刪除元素

237.刪除鏈表中的節(jié)點

class Solution {
public:
    void deleteNode(ListNode* node) {
        //struct ListNode* tmp=node->next;
        node->val=node->next->val;
        node->next=node->next->next;
        //delete tmp;
    }
};

203.移除鏈表元素

刪除鏈表中等于給定值 *val* 的所有節(jié)點。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if(head==NULL){
            return NULL;
        }
        struct ListNode* t=new ListNode(-1);
        t->next=head;
        
        struct ListNode* pre=t;
        while(pre->next!=NULL){
            if (pre->next->val==val){
                auto s=pre->next;
                pre->next=pre->next->next;   
                delete s;
            }else{
                pre=pre->next;
            }
        }
        return t->next;
    }
};

19.刪除鏈表的倒數(shù)第N個節(jié)點

給定一個鏈表,刪除鏈表的倒數(shù)第 n 個節(jié)點,并且返回鏈表的頭結點。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head==NULL || head->next==NULL){
            return NULL;
        }
        ListNode* first=head;
        ListNode* second=head;
        int i=0;
        // 先走n步,找到需要刪除的pre節(jié)點。
        while(i<n){ 
            first=first->next;
            i++;
        }
        //主要為了判斷是否是刪除首元素
        if(!first){
            return head -> next;    
        }
        while(first->next!=NULL){
            first=first->next;
            second=second->next;
        }
        ListNode* tmp=second->next;
        second->next=second->next->next;
        delete tmp;
        return head;
    }
};

83.刪除排序鏈表中的重復元素

給定一個排序鏈表,刪除所有重復的元素,使得每個元素只出現(xiàn)一次。

示例 1:

輸入: 1->1->2
輸出: 1->2
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* cur=head;
        while(cur!=NULL &&cur->next!=NULL){
            if(cur->next->val==cur->val){
                cur->next=cur->next->next;
            }else{
                cur=cur->next;
            }
        }
        return head;
        
    }
};

82.刪除排序鏈表中的重復元素II

給定一個排序鏈表,刪除所有含有重復數(shù)字的節(jié)點,只保留原始鏈表中 沒有重復出現(xiàn) 的數(shù)字。

提示:需要兩個指針;一個用來遍歷,另一個用來刪除。

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (head == NULL || head->next == NULL) return head;
        ListNode dummyNode(-1);
        ListNode* pre=&dummyNode;
        ListNode* cur=head;
        ListNode* tmp;
        int n,val;
        while(cur){
            tmp = cur;
            for(n=0,val=cur->val;cur!=NULL && cur->val==val;++n){
                cur=cur->next;
            }
            if (n==1){
                pre->next=tmp;
                pre=pre->next;
            }
        }
        pre->next=NULL;
        return dummyNode.next;
        
    }
};

1171.從鏈表中刪去總和值為0的連續(xù)節(jié)點

給你一個鏈表的頭節(jié)點 head,請你編寫代碼,反復刪去鏈表中由 總和 值為 0 的連續(xù)節(jié)點組成的序列,直到不存在這樣的序列為止。刪除完畢后,請你返回最終結果鏈表的頭節(jié)點。

你可以返回任何滿足題目要求的答案。(注意,下面示例中的所有序列,都是對 ListNode 對象序列化的表示。)

提示:我們可以考慮如果給的入?yún)⒉皇擎湵硎菙?shù)組的話,只需要求出前綴和,對于前綴和相同的項,那他們中間的部分即是可以消除掉的,比如以 [1, 2, 3, -3, 4] 為例,其前綴和數(shù)組為 [1, 3, 6, 3, 7] ,我們發(fā)現(xiàn)有兩項均為 3,則 6 和 第二個 3 所對應的原數(shù)組中的數(shù)字是可以消掉的。換成鏈表其實也是一樣的思路,把第一個 3 的 next 指向第二個 3 的 next 即可。

示例 1:

輸入:head = [1,2,-3,3,1]
輸出:[3,1]
提示:答案 [1,2,1] 也是正確的。
class Solution {
public:
    ListNode* removeZeroSumSublists(ListNode* head) {
        // 求和,當出現(xiàn)重復是 證明兩個重復節(jié)點之間的數(shù)據(jù)可以被刪除;
        // 然后繼續(xù)遞歸; 返回值為dummy->next;
        if (head==NULL){
            return head;
        }
        ListNode dummy(0);
        dummy.next=head;
        ListNode* dummyPtr=&dummy;
        map<int,ListNode*> map;
        map[0]=dummyPtr;
        int sum=0;
        while(head!=NULL){
            sum+=head->val;
            if(map.find(sum)!=map.end()){
                map[sum]->next=head->next;
                return removeZeroSumSublists(dummy.next);
            }else{
                map[sum]=head;
                head=head->next;
            }
        }
        return dummy.next;   
    }
};

3.合并鏈表

合并鏈表,需要注意頭節(jié)點dummyNode的使用。另外為了降低復雜度,可以考慮使用歸并算法。

21.合并兩個有序鏈表

將兩個有序鏈表合并為一個新的有序鏈表并返回。新鏈表是通過拼接給定的兩個鏈表的所有節(jié)點組成的。

示例:

輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode dummy = ListNode(-1);  //  創(chuàng)建??臻g對象即可。
        ListNode* prev = &dummy;   // 注意這里需要取地址&
        while(l1 != NULL && l2 != NULL) {
            if(l1->val <= l2->val) {
                prev->next = l1;
                l1 = l1->next;
            } else {
                prev->next = l2;
                l2 = l2->next;
            }
            prev = prev->next;
        }
        prev->next = l1 != NULL ? l1 : l2;
        return dummy.next;
    }
};

23.合并K個有序鏈表

題解:已經(jīng)實現(xiàn)了合并兩個有序鏈表了,那么其實可以使用歸并的思想進行合并k個鏈表,兩兩合并,時間復雜度為Nlogk

ListNode* mergeKLists(vector<ListNode*>& lists) {
        int len = lists.size();
        if(len < 1) return NULL;
        while(len>1){
            int m = (len + 1) / 2;
            for(int i=0;i<m && i+m < len;++i){
                lists[i] = mergeTwoLists(lists[i],lists[i+m]);
            }
            len = m;
        }
        return lists[0];
    }

2. 兩數(shù)相加

給出兩個 非空 的鏈表用來表示兩個非負的整數(shù)。其中,它們各自的位數(shù)是按照 逆序 的方式存儲的,并且它們的每個節(jié)點只能存儲 一位 數(shù)字。

如果,我們將這兩個數(shù)相加起來,則會返回一個新的鏈表來表示它們的和。

您可以假設除了數(shù)字 0 之外,這兩個數(shù)都不會以 0 開頭。

示例:

輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807

題解:新的節(jié)點等與兩個節(jié)點的(val+進位)%10; 下一輪的進位為(val+進位)/10;

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *root=new ListNode(0);
        ListNode *cur=root;
        ListNode *tmp;
        int c=0;
        while(l1!=NULL || l2!=NULL || c!=0){ //注意邊界條件,可能最后只有進位,需要創(chuàng)建新的節(jié)點
            int v1=l1?l1->val:0;
            int v2=l2?l2->val:0;
            int sum=v1+v2+c;
            c=sum/10;
            tmp=new ListNode(sum%10);
            cur->next=tmp;
            cur=tmp;
            if (l1){
                l1=l1->next; // 注意邊界條件
            }
            if (l2){
                l2=l2->next;
            }
        }
        return root->next;
    }
};

445.兩數(shù)相加II

給定兩個非空鏈表來代表兩個非負整數(shù)。數(shù)字最高位位于鏈表開始位置。它們的每個節(jié)點只存儲單個數(shù)字。將這兩數(shù)相加會返回一個新的鏈表。你可以假設除了數(shù)字 0 之外,這兩個數(shù)字都不會以零開頭。

進階:如果輸入鏈表不能修改該如何處理?換句話說,你不能對列表中的節(jié)點進行翻轉。

示例:

輸入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出: 7 -> 8 -> 0 -> 7

題解:當當前值的計算依賴下一項的計算時,可以使用遞歸的方法來實現(xiàn)回溯。當前值=(val+下一項的進位)/10。需要兩個鏈表等長。 另一種辦法是借助額外的結構,來實現(xiàn)。例如棧/數(shù)組等。

class Solution {
public:
   ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        vector<ListNode*> l1v,l2v;
        to_vector(l1,l1v);
        to_vector(l2,l2v);
        int i=l1v.size()-1, j=l2v.size()-1;
        int d = 0;
        ListNode *head = NULL;
        while(i >= 0 || j >= 0){
            if(i >= 0) d += l1v[i--]->val;
            if(j >= 0) d += l2v[j--]->val;
            ListNode* p = new ListNode(d%10);
            p->next = head;
            head = p;
            d /= 10;
        }
        if(d) {
            ListNode* p = new ListNode(d);
            p->next = head;
            head = p;
        }
        return head;
    }
    
    void to_vector(ListNode* a,vector<ListNode*>& v){
        while(a){
            v.push_back(a);
            a = a->next;
        }
    }
};

4.重排鏈表

鏈表按照指定的規(guī)則進行重新排序。比如首尾相連,交換鏈表節(jié)點等等。

24.兩兩交換鏈表中的節(jié)點

給定一個鏈表,兩兩交換其中相鄰的節(jié)點,并返回交換后的鏈表。

你不能只是單純的改變節(jié)點內部的值,而是需要實際的進行節(jié)點交換。

示例:

給定 1->2->3->4, 你應該返回 2->1->4->3.

題解:聲明dummyNode,pre指向dummy。 start指向1,end指向2. 讓pre.next=end;start.next=end.next;end.next=start. pre此刻再向前移動到start。

class Solution {
   public ListNode swapPairs(ListNode head) {
        ListNode pre = new ListNode(0);
        pre.next = head;
        ListNode temp = pre;
        while(temp.next != null && temp.next.next != null) {
            ListNode start = temp.next;
            ListNode end = temp.next.next;
            temp.next = end;
            start.next = end.next;
            end.next = start;
            temp = start;
        }
        return pre.next;
    }
}

25. k個一組反轉鏈表

給你一個鏈表,每 k 個節(jié)點一組進行翻轉,請你返回翻轉后的鏈表。

k 是一個正整數(shù),它的值小于或等于鏈表的長度。

如果節(jié)點總數(shù)不是 k 的整數(shù)倍,那么請將最后剩余的節(jié)點保持原有順序。

示例 :

給定這個鏈表:1->2->3->4->5

當 k = 2 時,應當返回: 2->1->4->3->5

當 k = 3 時,應當返回: 3->2->1->4->5

題解:合理利用cut函數(shù),每k個一組,切割成鏈表,然后反轉,然后注意進行鏈接。每次反轉前判斷下長度是否達到k。當對頭節(jié)點有進行操作是,注意使用dummyNode。 主要要保持鏈表直接的連接。

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
         ListNode dummyHead(0);
         dummyHead.next=head;
         auto cur=dummyHead.next;
         auto tail = &dummyHead;
         while(cur){
            auto left = cur;
            cur = cut(left, k); // left->@->@ right->@->@->@...
            tail->next = reverseList(left,k);
            while(tail->next){
                tail=tail->next;
            }
        }
        return dummyHead.next;
    }
    
    // k個一組進行反轉,采用cut函數(shù),連接的時候,注意保證尾部正確。
    
    ListNode* cut(ListNode* head,int k){
        auto p=head;
        while(--k && p){
            p=p->next;
        }
        if(!p){
            return nullptr;
        }
        auto next=p->next;
        p->next=nullptr;
        return next;
    }
    
    ListNode* reverseList(ListNode* head,int k) {
        struct ListNode* p=head;
        while(p!=NULL){
            p=p->next;
            k--;
        }
        if (k>0){
            return head;
        }
        struct ListNode* pre=NULL;
        struct ListNode* cur=head;
        struct ListNode* tmp=NULL;
        while(cur){
            tmp=cur->next;
            cur->next=pre;
            pre=cur;
            cur=tmp;
        }
        return pre;
    }
};

61.旋轉鏈表

給定一個鏈表,旋轉鏈表,將鏈表每個節(jié)點向右移動 k 個位置,其中 k 是非負數(shù)。

輸入: 1->2->3->4->5->NULL, k = 2
輸出: 4->5->1->2->3->NULL
解釋:
向右旋轉 1 步: 5->1->2->3->4->NULL
向右旋轉 2 步: 4->5->1->2->3->NULL

題解:方法一:先連接,再旋轉。再斷開。方法二:可以先整體逆序,然后前k個逆序,后n-k個逆序。

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if(head==NULL){
            return NULL;
        }
        if (head->next==NULL){
            return head;
        }
        ListNode *cur=head;
        int n=1;
        while(cur->next!=NULL){
            cur=cur->next;
            n++;
        }
        cur->next=head;
        ListNode *new_tail = head;
        int i=0;
        for(i=0;i<n - k % n - 1;++i){
            new_tail=new_tail->next;
        }
        ListNode* tmp=new_tail->next;
        new_tail->next=NULL;
        return tmp;
    }
};

143.重排鏈表

給定一個單鏈表 L:L0→L1→…→Ln-1→Ln ,
將其重新排列后變?yōu)椋?L0→Ln→L1→Ln-1→L2→Ln-2→…

你不能只是單純的改變節(jié)點內部的值,而是需要實際的進行節(jié)點交換。

給定鏈表 1->2->3->4, 重新排列為 1->4->2->3.
給定鏈表 1->2->3->4->5, 重新排列為 1->5->2->4->3.

經(jīng)典問題:考察快慢指針找中間節(jié)點;分隔鏈表;鏈表反轉,鏈表合并。需要考慮將中間節(jié)點分配到第一個鏈表還是第二個鏈表??紤]清楚邊界條件。

class Solution {
public:
    void reorderList(ListNode* head) {
        // 先快慢指針找到中間節(jié)點,然后對后面的反轉,最后合并
        if(head==NULL || head->next==NULL){
            return ;
        }
        ListNode* slow=head;
        ListNode* fast=head->next;
        while(fast!=NULL && fast->next!=NULL){
            fast=fast->next->next;
            slow=slow->next;
        }
        ListNode* l2=slow->next;
        slow->next=NULL;
        ListNode* l1=head;
        // 反轉list2
        ListNode* pre=NULL;
        ListNode* cur=l2;
        ListNode* next;
        while(cur!=NULL){
            next=cur->next;
            cur->next=pre;
            pre=cur;
            cur=next;
        }
        l2=pre;
        // 合并l1 l2
        while(l2!=NULL){
            slow=l1->next;
            fast=l2->next;
            l1->next=l2;
            l2->next=slow;
            l1=slow;
            l2=fast;
        }
        return;
    }
};

147.鏈表進行插入排序

class Solution {
public:
    ListNode* insertionSortList(ListNode* head) {
        if (head == NULL || head->next == NULL) {
            return head;
        }
        ListNode h(-1);
        h.next = head;
        ListNode* pre = &h;
        ListNode* cur = head;
        ListNode* lat;
        ListNode* tmp;
        while (cur != NULL) {
            lat = cur->next; // 記錄下一個要插入排序的值
            if (lat != NULL && lat->val < cur->val) { 
                // 只有 cur.next 比 cur 小才需要向前尋找插入點
                while (pre->next != NULL && pre->next->val < lat->val) { 
                    pre = pre->next; // 繼續(xù)向后移動
                }
                // 找到要插入的位置,此時 pre 節(jié)點后面的位置就是 lat 要插入的位置
                tmp = pre->next;
                pre->next = lat;
                cur->next = lat->next;// 保證cur不斷
                lat->next = tmp;
                pre = &h;// 由于每次都是從前往后找插入位置,所以需要每次插入完成后要讓 pre 復位
            } else {
                // 都這直接把 cur 指針指向到下一個
                cur = lat;
            }
        }
       return h.next;
    }
};

148.排序鏈表

O(n log n) 時間復雜度和常數(shù)級空間復雜度下,對鏈表進行排序。

題解:要求時間復雜度,考慮到歸并排序。兩兩合并,需要logn次。每次進行n個比較。需要注意的點;cut函數(shù)實現(xiàn),合并鏈個有序鏈表;dummyNode使用;鏈表的連接;等等。

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        ListNode dummyHead(0);
        dummyHead.next=head;
        auto p=head;
        int length=len(p);
        for (int size=1;size<length;size<<=1){
            auto cur=dummyHead.next;
            auto tail = &dummyHead;
            while(cur){
                 auto left = cur;
                 auto right = cut(left, size); // left->@->@ right->@->@->@...
                 cur = cut(right, size); // left->@->@ right->@->@  cur->@->..
                 tail->next = merge(left, right);
                while(tail->next){
                    tail=tail->next;
                }
            }
        }
        return dummyHead.next;
    }
    
    int len(ListNode *p){
        int length=0;
         while (p) {
            ++length;
            p = p->next;
        }
        return length;
    }
    
    ListNode* cut(ListNode* head,int n ){
        auto p=head;
        while(--n && p){
            p=p->next;
        }
        if(!p){
            return nullptr;
        }
        auto next=p->next;
        p->next=nullptr;
        return next;
    }
    ListNode* merge(ListNode* l1, ListNode* l2) {
        ListNode dummyHead(0);
        auto p = &dummyHead;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                p->next = l1;
                p = l1;
                l1 = l1->next;       
            } else {
                p->next = l2;
                p = l2;
                l2 = l2->next;
            }
        }
        p->next = l1 ? l1 : l2;
        return dummyHead.next;
    }
};

5.分隔鏈表

86.分隔鏈表

給定一個鏈表和一個特定值 x,對鏈表進行分隔,使得所有小于 x 的節(jié)點都在大于或等于 x 的節(jié)點之前。

你應當保留兩個分區(qū)中每個節(jié)點的初始相對位置。

題解:分解成兩個鏈表,再連接。注意dummyNode的使用。

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        if (head==NULL ||head->next==NULL){
            return head;
        }            
        ListNode l_head(-1);
        ListNode r_head(-1);
        ListNode* l=&l_head;
        ListNode* r=&r_head;
        auto l_tmp=l;
        auto r_tmp=r;
        while(head!=NULL){
            if(head->val<x){
                l->next=head;
                l=l->next;
            }else{
                r->next=head;
                r=r->next;
            }
            head=head->next;
        }
        l->next=r_tmp->next;
        r->next=NULL;
        return l_tmp->next;
    }
};

109.有序鏈表轉換二叉搜索樹

給定一個單鏈表,其中的元素按升序排序,將其轉換為高度平衡的二叉搜索樹。

本題中,一個高度平衡二叉樹是指一個二叉樹每個節(jié)點 的左右兩個子樹的高度差的絕對值不超過 1。

示例:

給定的有序鏈表: [-10, -3, 0, 5, 9],

一個可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面這個高度平衡二叉搜索樹:

      0
     / \
    -3   9
   /   /
 -10  5

題解:二叉搜索樹的前序遍歷為有序數(shù)組。那么對于鏈表中間節(jié)點即為跟節(jié)點。采用遞歸進行遍歷即可。

class Solution {
public:
    TreeNode* sortedListToBST(ListNode* head) {
        //核心思想,中心節(jié)點就是根節(jié)點,遞歸構造左右子節(jié)點。
        if(head==NULL){
            return NULL;
        }
        ListNode* mid=getMidNode(head);
        TreeNode* node=new TreeNode(mid->val);
        if (head==mid){
            return node;
        }
        node->left=sortedListToBST(head);
        node->right=sortedListToBST(mid->next);
        return node;
    }
    // 查找中間節(jié)點并返回。并分割left鏈表最后指向NULL。
    ListNode* getMidNode(ListNode *head){
        ListNode* pre=NULL;
        ListNode* slow=head;
        ListNode* fast=head;
        while(fast!=NULL && fast->next!=NULL){
            pre=slow;
            slow=slow->next;
            fast=fast->next->next;
        }
        if (pre!=NULL){
            pre->next=NULL;
        }
        return slow;
        
    }
   
};

725.分隔鏈表

給定一個頭結點為 root 的鏈表, 編寫一個函數(shù)以將鏈表分隔為 k 個連續(xù)的部分。

每部分的長度應該盡可能的相等: 任意兩部分的長度差距不能超過 1,也就是說可能有些部分為 null。

這k個部分應該按照在鏈表中出現(xiàn)的順序進行輸出,并且排在前面的部分的長度應該大于或等于后面的長度。

返回一個符合上述規(guī)則的鏈表的列表。

舉例: 1->2->3->4, k = 5 // 5 結果 [ [1], [2], [3], [4], null ]

示例 1:

輸入: 
root = [1, 2, 3], k = 5
輸出: [[1],[2],[3],[],[]]
解釋:
輸入輸出各部分都應該是鏈表,而不是數(shù)組。
例如, 輸入的結點 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一個輸出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一個元素 output[4] 為 null, 它代表了最后一個部分為空鏈表。
示例 2:

輸入: 
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
輸出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解釋:
輸入被分成了幾個連續(xù)的部分,并且每部分的長度相差不超過1.前面部分的長度大于等于后面部分的長度。
class Solution {
public:
    vector<ListNode*> splitListToParts(ListNode* root, int k) {
        // 遍歷root的長度,得到len。 
        // if len<=k ; 那么每個都是獨立節(jié)點,再增加k-len個空節(jié)點。
        // if len>k; 那么切分為 len/k 個片段;其中 前 len%k個片段,長度為 len/k + 1;
        // 實現(xiàn)cut(ListNode* head,int k); 切割前k個節(jié)點,返回第k+1個節(jié)點后的鏈表
        // 循環(huán)切割,時間復雜度為O(N)
        vector<ListNode*> vec;
        ListNode* cur=root;
        int len=getLen(root);
        if (len<k){
            for(int i=0;i<k;i++){
                vec.push_back(cur);
                cur=cut(cur,1);
            }
        }else{
            int step=len/k;
            int m=len%k;
            int i=0;
            for (i=0;i<m;i++){
                vec.push_back(cur);
                cur=cut(cur,step+1);
            }
            for(i=m;i<k;i++){
                vec.push_back(cur);
                cur=cut(cur,step);
            }
        }
        return vec;
    }
    
    int getLen(ListNode* head){
        if(head==NULL){
            return 0;
        }
        int len=0;
        ListNode* cur=head;
        while(cur){
            cur=cur->next;
            len++;
        }
        return len;
    }
    
    ListNode* cut(ListNode* head,int k){
        while(k>1 && head){
            head=head->next;
            k--;
        }
        if (head==NULL){
            return NULL;
        }
        ListNode* tmp=head->next;
        head->next=NULL;
        return tmp;
    }
};

6.使用額外結構降低時間復雜度

138.復制帶隨機指針的鏈表

給定一個鏈表,每個節(jié)點包含一個額外增加的隨機指針,該指針可以指向鏈表中的任何節(jié)點或空節(jié)點。

要求返回這個鏈表的深拷貝

題解:先全部復制一遍,再進行指針關聯(lián)

class Node {
public:
    int val;
    Node* next;
    Node* random;

    Node() {}

    Node(int _val, Node* _next, Node* _random) {
        val = _val;
        next = _next;
        random = _random;
    }
};
*/
class Solution {
public:
    // hash 空間復雜O(N) 
    // 原地復制 空間復雜O(1) 復制節(jié)點 復制random 拆鏈表
    Node* copyRandomList(Node* head) {
        if(head==nullptr){
            return nullptr;
        }
        map<Node*,Node*> map;
        Node* cur=head;
        while(cur){
            map[cur]=new Node(cur->val,nullptr,nullptr);
            cur=cur->next;
        }
        cur=head;
        while(cur){
            map[cur]->next=map[cur->next];
            map[cur]->random=map[cur->random];
            cur=cur->next;
        }
        return map[head];
    }
};

817.鏈表組件

給定一個鏈表(鏈表結點包含一個整型值)的頭結點 head。

同時給定列表 G,該列表是上述鏈表中整型值的一個子集。

返回列表 G 中組件的個數(shù),這里對組件的定義為:鏈表中一段最長連續(xù)結點的值(該值必須在列表 G 中)構成的集合。

示例 1:

輸入: 
head: 0->1->2->3
G = [0, 1, 3]
輸出: 2
解釋: 
鏈表中,0 和 1 是相連接的,且 G 中不包含 2,所以 [0, 1] 是 G 的一個組件,同理 [3] 也是一個組件,故返回 2。
示例 2:

輸入: 
head: 0->1->2->3->4
G = [0, 3, 1, 4]
輸出: 2
解釋: 
鏈表中,0 和 1 是相連接的,3 和 4 是相連接的,所以 [0, 1] 和 [3, 4] 是兩個組件,故返回 2。

class Solution {
public:
    int numComponents(ListNode* head, vector<int>& G) {
        set<int> m_set;
        for (int i=0;i<G.size();i++){
            m_set.insert(G[i]);
        }
        ListNode* cur=head;
        int num = 0;
        while(cur){
            if(m_set.find(cur->val)!= m_set.end() && (cur->next==NULL || m_set.find(cur->next->val)== m_set.end())){
                num++;
            }
            cur=cur->next;
        }
        return num;
        
    }
};

1019.鏈表中下一個更大節(jié)點

給出一個以頭節(jié)點 head 作為第一個節(jié)點的鏈表。鏈表中的節(jié)點分別編號為:node_1, node_2, node_3, ... 。

每個節(jié)點都可能有下一個更大值(next larger value):對于 node_i,如果其 next_larger(node_i) 是 node_j.val,那么就有 j > i 且 node_j.val > node_i.val,而 j 是可能的選項中最小的那個。如果不存在這樣的 j,那么下一個更大值為 0 。

返回整數(shù)答案數(shù)組 answer,其中 answer[i] = next_larger(node_{i+1}) 。

注意:在下面的示例中,諸如 [2,1,5] 這樣的輸入(不是輸出)是鏈表的序列化表示,其頭節(jié)點的值為 2,第二個節(jié)點值為 1,第三個節(jié)點值為 5 。

示例 1:

輸入:[2,1,5]
輸出:[5,5,0]
示例 2:

輸入:[2,7,4,3,5]
輸出:[7,0,5,5,0]
示例 3:

輸入:[1,7,5,1,9,2,5,1]
輸出:[7,9,9,9,0,5,0,0]
class Solution {
public:
   //題解,使用棧先進后出。找到第一個比較大的就更新對應的數(shù)組值。
    vector<int> nextLargerNodes(ListNode* head) {
        int count = 0; //計數(shù),作為下標
        vector<int> result;
        stack<pair<int, int>> tmp; //first為val,second為下標
        while (head)
        {
            result.push_back(0); //給result數(shù)組后面+0,1為保證長度,2是默認值(后無更大的值的話)為0
            while (!tmp.empty() &&
                   head->val > tmp.top().first) //棧不為空且head指針的val值大于棧頂?shù)脑氐闹?            {
                result[tmp.top().second] = head->val; //result數(shù)組修改,滿足題意要求的最大值,然后出棧,繼續(xù)循環(huán)
                tmp.pop();
            }
            tmp.push(make_pair(head->val, count++)); //count++計數(shù)
            head = head->next; //下一個節(jié)點
        }
        return result;       
    }
};

7.快慢指針

160.相交鏈表

編寫一個程序,找到兩個單鏈表相交的起始節(jié)點。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int aLen=0;
        int bLen=0;
        ListNode *curA=headA;
        ListNode *curB=headB;
        while(curA!=NULL){
            curA=curA->next;
            aLen++;
        }
        while(curB!=NULL){
            curB=curB->next;
            bLen++;
        }
        int i=0;
        curA=headA;
        curB=headB;
        if (aLen>bLen){
            for(i=0;i<aLen-bLen;i++){
                curA=curA->next;
            }
        }else{
            for(i=0;i<bLen-aLen;i++){
                curB=curB->next;
            }   
        }
        while(curA){
            if(curA==curB){
                return curA;
            }else{
                curA=curA->next;
                curB=curB->next;
                i++;
            } 
        }
        return NULL;
    }
};

234.回文鏈表

請判斷一個鏈表是否為回文鏈表。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(head==NULL||head->next==NULL){
            return true;
        }
        ListNode* slow=head;
        ListNode* fast=head->next;
        while(fast!=NULL && fast->next!=NULL){
            fast=fast->next->next;
            slow=slow->next;
        }
        ListNode* r=resver(slow->next);
        while(r!=NULL){
            if(r->val!=head->val){
                return false;
            }
            r=r->next;
            head=head->next;
        }
        return true;
    }
    
    ListNode* resver(ListNode* head){
        if(head==NULL||head->next==NULL){
            return head;
        }
        ListNode* pre=NULL;
        ListNode* cur=head;
        ListNode* next;
        while(cur!=NULL){
            next=cur->next;
            cur->next=pre;
            pre=cur;
            cur=next;
        }
        return pre;
    }
};

328.奇偶鏈表

給定一個單鏈表,把所有的奇數(shù)節(jié)點和偶數(shù)節(jié)點分別排在一起。請注意,這里的奇數(shù)節(jié)點和偶數(shù)節(jié)點指的是節(jié)點編號的奇偶性,而不是節(jié)點的值的奇偶性。

請嘗試使用原地算法完成。你的算法的空間復雜度應為 O(1),時間復雜度應為 O(nodes),nodes 為節(jié)點總數(shù)。

示例 1:

輸入: 1->2->3->4->5->NULL
輸出: 1->3->5->2->4->NULL
示例 2:

輸入: 2->1->3->5->6->4->7->NULL
輸出: 2->3->6->7->1->5->4->NULL
說明:

應當保持奇數(shù)節(jié)點和偶數(shù)節(jié)點的相對順序。
鏈表的第一個節(jié)點視為奇數(shù)節(jié)點,第二個節(jié)點視為偶數(shù)節(jié)點,以此類推。

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head==NULL||head->next==NULL){
            return head;
        }
        ListNode* t1=head;
        ListNode* t2=head->next;
        ListNode* t2head=t2; //必須先保存,才可以保證鏈接。因為后續(xù)head->next 變了。
        while(t2!=NULL && t2->next!=NULL){
            t1->next=t2->next;
            t1=t1->next;
            t2->next=t1->next;
            t2=t2->next;
        }
        t1->next=t2head;
        return head;
    }
};
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容