1. 程式人生 > 實用技巧 >資料結構學習-LeetCode21.合併兩個有序連結串列C語言

資料結構學習-LeetCode21.合併兩個有序連結串列C語言

LeetCode21.合併兩個有序連結串列
難度 簡單
將兩個升序連結串列合併為一個新的 升序 連結串列並返回。新連結串列是通過拼接給定的兩個連結串列的所有節點組成的。

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

資料結構是程式設計的基礎,它決定了一個程式設計師的發展潛力,所以練好資料結構還是很重要的,
最近我會先在力扣上將資料結構的題寫一遍(先用C語言寫),並將總結與感想記錄在我的部落格裡。
(Ps:蒟蒻一枚,求大佬輕點噴)

對於這道題目,最開始我的做法是暴力破解,大概思路如下:

先分別找到list1與list2連結串列中的最小節點,然後比較將更小的那個節點新增到新的連結串列中去,用while迴圈這一過程直至有一個連結串列為NULL結束迴圈,
然後將兩個連結串列中不為NULL的連結串列新增到新連結串列中去,最後返回新連結串列。

看完官方題解後發現這個方法屬實笨(~ ̄(OO) ̄)ブ,又耗時間,又佔記憶體,需要開好幾個指標,
不過由於測試資料不穩定的原因,最終的測試結果一點波動,最好的情況耗時0ms,打敗100%使用者(我當時一臉懵逼,不應該呀,這個演算法),最壞8ms,打敗8%使用者......
可能也許maybe時間複雜度為O(n3)?空間複雜度我也不會算,開了這麼多指標估計也不會低,至少是O(n+m)吧....

程式碼如下:
我先定義了這麼多指標,,,看著就令人頭皮發麻

    struct ListNode* newList;   //新連結串列
    struct ListNode* headL1;    //list1連結串列指標
    struct ListNode* headL2;    //list2連結串列指標
    struct ListNode* headNew;   //新連結串列指標
    struct ListNode* newNode;   //新增的新節點
    struct ListNode* ptrA;  //查詢list1中最小值的指標
    struct ListNode* ptrB;  //查詢list2中最小值的指標

然後進行初始化設定:

    newList = (ListNode*)malloc(sizeof(ListNode));
    headL1 = l1->next;
    headL2 = l2->next;
    headNew = newList;

接著就開始在while迴圈裡面依次查詢最小值,然後比較,把更小的值新增到新連結串列中去:

while (headL1 != NULL && headL2 != NULL){
    //1.分別找到兩個連結串列中的最小值 
    int minA = headL1->val;
    int minB = headL2->val;
    ptrA = headL1;
    ptrB = headL2;
    //迴圈連結串列1,找到最小值
    while (ptrA != NULL){
	if (minA > ptrA->val) minA = ptrA->val;
        ptrA = ptrA->next;
    }
    //迴圈連結串列2,找到最小值
    while (ptrB != NULL){
        if (minB > ptrB->val) minB = ptrB->val;
	ptrB = ptrB->next;	
    }
    //2.找出兩個最小值中的最小值,並將其賦給新的結點 
    newNode = (ListNode*)malloc(sizeof(ListNode));
    if (minA < minB){
        newNode->val = minA;
        //如果最小值在list1中,就將list1的指標往後移
        headL1 = headL1->next;
    }else{
	newNode->val = minB;
        //如果最小值在list2中,就將list2的指標往後移
	headL2 = headL2->next;
    }
    //將節點賦給新連結串列 
    headNew->next = newNode;
    //讓新連結串列的指標往後移
    headNew = newNode;
}

好吧,我知道很蠢,求噴輕點QAQ
接下來就是把兩個連結串列中比較長的那一個連結串列未遍歷完的節點(已排好序的)新增到新連結串列上去
這是我自己想出來的做法:

	//如果第一個連結串列為空了,將第二個連結串列剩餘的新增到新連結串列 
	if (headL1 == NULL && headL2 != NULL){
		ptrB = headL2;
		while (ptrB != NULL){
			newNode = (ListNode*)malloc(sizeof(ListNode));
			newNode->val = ptrB->val;
			headNew->next = newNode;
			headNew = newNode;
			ptrB = ptrB->next;
		}
	}
	//如果第二個連結串列為空了,將第一個連結串列剩餘的新增到新連結串列 
	if (headL1 != NULL && headL2 == NULL){
		ptrA = headL1;
		while (ptrA != NULL){
			newNode = (ListNode*)malloc(sizeof(ListNode));
			newNode->val = ptrA->val;
			headNew->next = newNode;
			headNew = newNode;
			ptrA = ptrA->next;
		}
	}

這是我看了官方題解後發做法:

    headNew->next = headL1 == NULL ? headL2 : headL1;

接著返回newList->next就可以了。。。
讓我們快點跳過這個羞恥的做法,去看看官解吧QAQ

解法一:遞迴 時間複雜度為O(n+m) 空間複雜度為O(n+m)

這個已經可以把我的解法按在地上摩擦了...
先上程式碼再講解:

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    if (l1 == NULL){
        return l2;
    }else if (l2 == NULL){
	return l1;
    }else if (l1->val < l2->val){
	l1->next = mergeTwoLists(l1->next, l2);
	return l1;
    }else{
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
}

首先,要用遞迴就得有出口,不給函式提供出口的話就會一直死迴圈的執行下去

我們的出口就是當兩個連結串列其中一個遍歷完後(即指標指向為NULL)就退出迴圈,並將另一個連結串列返回(也就是把剩下的數接到最後一個指標上面去)
在這個解法上我們不用新開連結串列,只需要假象存在一個新的連結串列,然後list1與list2分別為兩個連結串列的指標,
讓指標指向的元素比較大小,較小的元素指標後移,下一次遞迴裡我們再比較當前兩個指標指向元素的大小

如圖有兩個連結串列listA與liistB,遞迴時我們先比較A1與B1大小,1 < 2,
所以listA指標後移,listB指標不變
文字太抽象,直接上圖

(gif圖片大小咋調整喲QAQ)
最終遞迴結束後將節點從6依次返回

解法二:迭代

程式碼先走起

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode* preHead;
    preHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* prev;
    prev = preHead;
    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 ? l2 : l1; 
    return preHead->next;
}

這裡我們需要建立兩個指標變數,一個為頭指標preHead方便之後返回最終連結串列,另一個為prev用來指向當前元素的(即定位功能,類似於方向標)(注意,這裡了l1和l2也是指向元素的作用)
同樣的,迴圈的終止條件為兩個連結串列中有其中一個被遍歷完就終止迴圈
在迴圈中比較l1和l2指向的元素大小,讓prev指標指向其中較小元素的節點,依此迴圈下去,迴圈結束後依然把剩餘的節點接到prev後面。
法二可以直接去看力扣官方題解的動圖,講的很詳細。
按我的理解,這裡的preHead就像一個無家可歸的孩子,而prev就是給他指向回家路的好心人,他要判別哪條路才能讓preHead回到家,這樣他就不得不比較每個元素的大小,選取較小的元素作為下一條正確的回家路,最終prev將所有路連起來,然後我們返回第一條路(即preHead->next),讓這位孩紙按著連結串列的路走回家。

如果有不好的地方歡迎大家在評論區討論呀

(當然這題比較簡單,以後我會持續更新學習資料結構的記錄的)