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; }