1. 程式人生 > >leetcode經典題系列------連結串列

leetcode經典題系列------連結串列

定義如下:

struct ListNode{
    int val;
    ListNode *next;
    ListNode(int x):val(x),next(NULL){}
};

第一題:連結串列翻轉 easy

ListNode * reverseList(ListNode * head)
{
    ListNode *new_head=NULL;//指向新連結串列頭節點的指標
    while(head){
        ListNode *next=  head->next;//備份head->next
        head->next=new_head;//
更新hea睨zhid->next; new_head=head;//移動new_head; head=next;//遍歷連結串列 } return new_head;//返回新連結串列頭節點 }

第二題 連結串列翻轉 變式

描述:

已知連結串列頭節點指標head,將連結串列從位置m到n逆序。(不申請額 外空間)
比如m=2,n=4,表示從2個節點到第四個節點逆向 1<=m<=n<=連結串列長度

思路:

尋找關鍵點
1 逆置段頭節點的前驅
2 逆置前頭節點變為你只後尾節點
3 逆置前尾節點變為你只後頭節點
4 逆置段尾節點的後繼
步驟一:將head向前移動m-1個位置,找到開始逆置的節點,記錄該節點的前驅和該節點
步驟二:從head開始,逆置change_len=n-m+1d個節點
步驟三:將pre_head與new_head連線,modify_list_tail與head相連

ListNode * reverseBetween(ListNode *head,int m ,int n){
    int change_len=n-m+1;//計算需要逆置的節點個數
    ListNode *pre_head=NULL;//初始化開始逆置的節點的前驅
    ListNode *result=head;//最終轉換後的連結串列頭節點,非特殊情況即為head
    while (head&&--m) {//將head向前移動m-1個位置
        pre_head=head;
        head=head->next;
    }
    
//將modify_list_tail指向當前的head,即逆置後的連結串列尾 ListNode *modify_list_tail=head; ListNode * new_head=NULL; while (head&&change_len) {//逆置change_len個節點 ListNode *next=head->next; head->next=new_head; new_head=head; head=next; change_len--;//每完成一個節點逆置,change_len--; } modify_list_tail->next=head;//連線逆置後的連結串列尾與逆置段的後一個節點 //如果pre_head不為空,說明不是從第一個節點開始逆置的 m>1 //將逆置連結串列開始的節點前驅與逆置後的頭節點連線 if(pre_head){ pre_head->next=new_head; } else{ //如果pre_head為空,說明m=1從第一個節點逆置,結果為逆置後的頭節點 result=new_head; } return result; }

第三題 求兩個連結串列的交點

描述:

已知連結串列A的頭節點指標headA,連結串列B的頭節點指標headB,兩 個連結串列相交,求兩連結串列交點對應的節點

要求:

1.如果兩個連結串列沒有交點,則返回NULL
2.在求交點的過程中,不可以破壞連結串列的結構或者修改連結串列的資料域
3.可以確保傳入的連結串列A與連結串列B沒有任何環
4*.實現演算法儘可能使時間複雜度O(n),空間複雜度O(1)

思路:

步驟一:計算headA連結串列長度,計算headB連結串列長度,較長的連結串列多出的長度
步驟二:將較長連結串列的指標移動到和較短連結串列指標對其的位置
步驟三:headA和headB同時移動,當兩指標h指向同一個節點時,找到了

//計算連結串列長度
int get_list_length(ListNode *head){
    int len=0;
    while (head) {
        len++;
        head=head->next;
    }
    return len;
}
//向前移動
ListNode *forward_long_list(int long_len,int short_len,ListNode *head){
    int delta=long_len-short_len;
    while (head&&delta) {
        head=head->next;
        delta--;
    }
    return head;
}
//得到交點
ListNode *getIntersectionNode(ListNode *headA,ListNode *headB){
    int list_A_length=get_list_length(headA);
    int list_B_length=get_list_length(headB);
    if(list_A_length>list_B_length){
        headA=forward_long_list(list_A_length, list_B_length, headA);
    }
    else{
        headB=forward_long_list(list_B_length, list_A_length, headB);
    }
    while (headB&&headA) {
        if(headB==headA){
            return headA;
        }
        headA=headA->next;
        headB=headB->next;
    }
    return NULL;
    
}

第四題:連結串列求環

描述:

已知連結串列中可能存在環,若有環返回環起始節點,否則返回NULL。

思路:

快慢指標賽跑

先求出相遇點,然後根據數學公式
從相遇點出發和從head出發,相遇點就是 環入口點

ListNode *detectCycle(ListNode *head){
    ListNode *fast=head;
    ListNode * slow=head;
    ListNode *meet=NULL;
    while (fast) {
        slow=slow->next;
        fast=fast->next;
        if(!fast){
            return NULL;
        }
        fast=fast->next;
        if(fast==slow){
            meet=fast;
            break;
        }
    }
    if(meet==NULL){
        return NULL;
    }
    while (head&&meet) {
        if(head==meet){
            return head;
        }
        head=head->next;
        meet=meet->next;
    }
    return NULL;
}

第五題:連結串列劃分

描述:

已知連結串列頭指標head與數值x,將所有小於x的節點放在大於或等於x 的節點前,且保持這些節點的原來的相對位置。

思路:

巧用臨時頭節點,然後用連結串列尾插法

ListNode * partition(ListNode *head,int x){
    ListNode less_head(0);
    ListNode more_head(0);
    ListNode *less_ptr=&less_head;
    ListNode *more_ptr=&more_head;
    while (head) {
        if(head->val<x){
            less_ptr->next=head;
            less_ptr=head;
        }
        else{
            more_ptr->next=head;
            more_ptr=head;
        }
        head=head->next;
    }
    less_ptr->next=more_head.next;
    more_ptr->next=NULL;
    return less_head.next;
}

第六題:複雜連結串列的深度拷貝

描述:

已知一個複雜的連結串列,節點中有一個指向本連結串列任意某個節點的隨機指 針(也可以為空),求這個連結串列的深度拷貝。

思路:

通過Map對映,這裡用2個Map

第一個Map
遍歷老連結串列,Map[節點地址]=節點索引位置 儲存
第二個Map
遍歷老連結串列,Map[節點位置]=新連結串列節點地址

struct RandomListNode{
    int label;
    RandomListNode *next,*random;
    RandomListNode(int x):label(x),next(NULL),random(NULL){}
};

RandomListNode * copyRandomList(RandomListNode *head)
{
    std::map<RandomListNode*,int> node_map;//地址到節點位置的map
    std::vector<RandomListNode*> node_vec;//使用vector根據儲存節點位置訪問的地址
    RandomListNode *ptr=head;
    int i=0;
    while (ptr) {
        node_vec.push_back(new RandomListNode(ptr->label));
        node_map[ptr]=i;
        ptr=ptr->next;
        i++;
    }
    node_vec.push_back(0);//多加一個元素,後面i+1的時候方便處理
    ptr=head;
    i=0; //再次遍歷原始列表 連線新連結串列的next指標,random指標
    while (ptr) {
        node_vec[i]->next=node_vec[i+1];//連線新連結串列的next指標
        if(ptr->random){
            int id=node_map[ptr->random];//獲取索引
            node_vec[i]->random=node_vec[id];
        }
        ptr=ptr->next;
        i++;
    }
    return node_vec[0];
}

第七題:2個連結串列的合併

描述:

已知兩個已排序連結串列頭節點指標l1與l2,將這兩個連結串列合併,合併後仍為 有序的,返回合併後的頭節點。

思路:

藉助頭節點

ListNode *mergeTwoLists(ListNode *l1,ListNode *l2){
    ListNode temp_head(0);
    ListNode *pre=&temp_head;
    while (l1&&l2) {
        if(l1->val < l2->val){
            pre->next=l1;
            l1=l1->next;
        }
        else{
            pre->next=l2;
            l2=l2->next;
        }
        pre=pre->next;
    }
    if(l1){
        pre->next=l1;
    }
    if(l2){
        pre->next=l2;
    }
    return temp_head.next;
}

第八題:多個排序連結串列的合併

思路:

採用分治法效率最高

演算法負責度 為O(kNlogk),k為連結串列個數,n為每個連結串列的長度
比如 每個連結串列長度為3,k為8,第一輪,進行k/2=4次比較(8/2=4,4/2=2),每次處理2n個數字,也就是6個(2個連結串列為6)
第二輪進行8/4=2次比較,每次比較4n個數字,第三輪是最後一輪,進行1次比較,處理8*3個數

ListNode* mergeKLists(std::vector<ListNode*>&lists)
{
    if(lists.size()==0){
        return NULL;
    }
    if(lists.size()==1){
        return lists[0];
    }
    if(lists.size()==2){
        return mergeTwoLists(lists[0], lists[1]);
    }
    int mid=(int)lists.size()/2;
    std::vector<ListNode*>sub1_lists;
    std::vector<ListNode*>sub2_lists;//拆分lists為兩個子lists
    for (int i=0; i<mid; i++) {
        sub1_lists.push_back(lists[i]);
    }
    for (int i=mid; i<lists.size(); i++) {
        sub2_lists.push_back(lists[i]);
    }
    ListNode *l1=mergeKLists(sub1_lists);
    ListNode *l2=mergeKLists(sub2_lists);
    return mergeTwoLists(l1, l2);//分制處理
}

第九題:交換一個單鏈表 兩兩相鄰的兩個元素

思路1:藉助3個指標進行移動

ListNode *swapPairs(ListNode *head){
    if(head==NULL)
        return NULL;
    ListNode preHead(0);
    preHead.next=head;
    ListNode *left=&preHead;
    ListNode *mid=head;
    if(head->next==NULL){
        return head;
    }
     ListNode *right=head->next;
    while (mid&&mid->next) {
        mid->next=right->next;
        right->next=mid;
        left->next=right;

        left=mid;
        mid=left->next;
        if(mid){
            right=mid->next;
        }
    }
    return  preHead.next;
    
}

思路2:遞迴

藉助temp儲存節點的下一個節點,然後本節點的next指向遞迴函式返回的指標,最後temp的next指向head

ListNode *swapPairs(ListNode *head){

    if(!head) return NULL;
             if(!head->next) return head;
             ListNode* temp=head->next;
             head->next=swapPairs(temp->next);
             temp->next=head;
             return temp;
}