文章簡述
大家好,本篇是個人的第 5 篇文章
從本篇文章開始,分享關於連結串列的題目為中等難度,本次共有 3 道題目。
一,兩數相加
1.1 題目分析
題中寫到數字是按照逆序的方式儲存,從進位的角度看,兩兩節點相加我們是可以直接將進位傳遞到下一組兩兩節點相加。
比如題中第二組節點【4】和節點【6】相加結果為 10,而 10 就需要進位,也就是說該節點只能儲存數字【0】,而進位【1】就要傳遞到下一組節點相加。
那再整理下思路。
如果兩個連結串列的節點數是相等的,那隻需要依次將兩兩節點進行相加。如果節點數是不相等的,比如。
L2 連結串列少了一個節點,像這種情況我們就需要在高位用【0】進行補位。
我們再回到題中的案例,而上面說的位數不夠也是需要考慮的一種情況。再一步步分析下如何進行兩兩節點相加。
第一組節點相加為2+5=7,不滿足進位。建立一個新的連結串列儲存相加後的數,那此時連結串列第一個節點數為【7】。
接著是4+6=10,此時滿足進位要求,按照題目要求和我們上面的分析,需要將低位【0】儲存到節點,高位【1】傳遞到下一組節點。
那現在進行最後一組相加3+4=7,但是還有重要一步不能丟,即上一組節點相加時,還有高位進 1,那最後的結果是 3+4+1=8。
最後將上面相加的結果用連結串列進行儲存,那麼結果為。
同理,如果位數不足時用【0】進行補位也是一樣的方式。
1.2 程式碼分析
老方式,先建立單連結串列
// 建立連結串列-L1
ListNode l1 = new ListNode(2);
ListNode l2 = new ListNode(4);
ListNode l3 = new ListNode(3);
ListNodeFun listNodeFun = new ListNodeFun();
listNodeFun.add(l1);
listNodeFun.add(l2);
ListNode listNode1 = listNodeFun.add(l3);
ListNodeFun listNodeFun2 = new ListNodeFun();
// 建立連結串列-L2
ListNode l11 = new ListNode(5);
ListNode l22 = new ListNode(6);
ListNode l33 = new ListNode(4);
listNodeFun2.add(l11);
listNodeFun2.add(l22);
ListNode listNode2 = listNodeFun2.add(l33);
兩數相加程式碼
if(null == l1 || null == l2){
return null;
}
// 初始化頭指標
ListNode head = new ListNode();
ListNode cur = head;
// 定義變數儲存進位值
int temp = 0;
while(null != l1 || null != l2){
// 獲取每個節點的值
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
// 兩數相加
int sum = x + y + temp;
// 獲取相加結果
temp = sum / 10;
// 獲取低位(個位)
sum = sum % 10;
// 建立新的節點
cur.next = new ListNode(sum);
// 移動指標
cur = cur.next;
// 移動連結串列指標,要判斷為空,否則會空針
if(null != l1){
l1 = l1.next;
}
if(null != l2){
l2 = l2.next;
}
}
if(1 == temp){
cur.next = new ListNode(1);
}
return head.next;
}
1.3 debug 除錯
第一步,2+5
兩個連結串列的首節點相加,結果為 7。
【7】不需要進位,建立新的連結串列進行儲存。
第二步,4+6
結果為 10 就需要進位,將個位上的【0】儲存到節點中,十位上【1】需要進行進位。
第三步,3+4+1
最後看下執行結果
簡單總結下,這道題並不算難,但需要考慮清楚當節點相加時是否需要進行補位的情況。
二,刪除連結串列的倒數第 N 個結點
2.1 題目分析
這道題,是不是似曾相識?
沒錯,在上一篇文章中《連結串列演算法題二,還原題目,用 debug 除錯搞懂每一道題》有一道題是【連結串列中倒數第 k 個節點】。但是這兩道題之間略有不同,上一篇文章中的題目是返回倒數第 K 個節點,本道題中是移除第 K 個節點,返回其他完整連結串列。
那麼這兩道題相似度很高,是不是套路也是一樣。
上一道題我們使用了雙指標的方式,那本道題也是一樣的。所以上一道題如果搞懂了,那這道所謂中等級別的題也就成簡單級別的了。雖然本人目前題量不多,但是如果善於總結的話,套路確實很接近,反正這個題我是直接寫出來了,哈哈(開玩笑)。
話又說回來,分析題中的含義,假設移除節點【4】,按照雙指標的方式,那就是一個慢指標指向節點【3】,快指標指向節點【5】。將節點【3】的下下個 next 指向節點【5】,即可移除節點【4】。
參考上一道題的方式,需要將【fast】快指標先移動 K 個節點,初始化指標位置。
注意:移除節點後,是需要反回其它完整的連結串列節點。但是有一種情況有坑,先看下圖
連結串列只有一個節點並移除,正確結果應該是返回空。像這種情況是不能直接返回 head 連結串列,因此是需要建立頭指標來指引原始的連結串列,如下圖。
所以定義雙指標的起始節點位置就是 head 節點。
按照套路先將快指標移動 K 個節點
剩下的操作即移動快慢指標,直到 fast 指標移動到最後一個節點。
(1)
(2)
(3)
最後更改 slow 指標直接指向 fast 指標指向的節點即可。
2.2 程式碼分析
建立連結串列的程式碼同上題一樣,本道題只需要建立一個 1-5 節點的連結串列。
直接貼上刪除節點的程式碼
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = new ListNode();
pre.next = head;
// 定義雙指標
ListNode slow = pre;
ListNode fast = head;
// 先將快指標移動n個節點
while(--n>0){
fast = fast.next;
}
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return pre.next;
}
2.3 debug 除錯
我們先 debug 除錯看下初始化節點位置後,快慢指標的位置。
接著進入第 2 個 while 迴圈,將剩餘的節點遍歷完。
這一步 slow 指標到節點【1】,fast 指標到節點【3】
下一步 slow 指標到節點【2】,fast 指標到節點【4】,直接看最後一步 slow 指標到節點【3】,fast 指標到節點【5】
節點【5】即最後一個節點,此時退出迴圈,最後將 slow 指標 next 指向 fast 指標指向的節點。
執行結果
三,兩兩互動連結串列中的節點
3.1 題目分析
說來慚愧,這道題當時寫的時候基本沒有什麼思路,結果還是看了題解才寫出來的。
現在想想這真是道靈魂題啊,真是沒想到還能用遞迴去寫這道題,不得不說真是萬能的遞迴啊(主要本人太菜,哈哈)。
遞迴的方式在於如果是偶數連結串列,將兩兩節點相互交換;如果是奇數連結串列,那最後一個節點保持不動,下面用 debug 除錯會看的清楚些。
將在偶數位上的節點指向上一個奇數位的節點,使用遞迴依次類推來遍歷整個連結串列。
大致的思路是這樣,使用遞迴將連結串列遍歷結束,然後返回最後節點【4】並指向上一個節點【3】;接著返回遞迴的結果【2】指向上一個節點【1】,而節點【1】也是指向節點【4】。
接著下次遞迴
按照規則分析,節點【4】與節點【3】交換位置,那返回的就是節點【4】
現在回到第一步遞迴的結果,當時 head 指向的節點【1】,那麼 head.next 指向誰?現在遞迴結果返回節點【4】,因此 head.next 也就指向的是節點【4】
最後節點【1】與節點【2】交換位置,就成了最後的連結串列交換結果。
這樣分析還是很抽象,下面用 debug 除錯走一遍就清晰了。
3.2 程式碼分析
遞迴的程式碼還是比較簡單,先貼上來。
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode nextNode = head.next;
head.next = swapPairs(nextNode.next);
nextNode.next = head;
return nextNode;
}
3.3 debug 除錯
第一步,節點【1】和節點【2】
開始進入遞迴迴圈,此時 nextNode 節點為【2】,那該節點的下一個節點為【3】
第二步,節點【3】和節點【4】
現在 nextNode 節點為【4】,再次進入遞迴迴圈時,節點【4】的 next 就為 null,因為節點【4】為最後一個節點,開始結束遞迴。
現在開始返回遞迴的結果,首先返回的就是節點【3】和節點【4】
再看第 43 行程式碼,將節點【4】下一個節點指向了節點【3】,並返回了節點【4】
接著返回節點【1】和節點【2】
注意:上一步遞迴中,我們返回的結果為節點【4】
上圖中看到 head 節點為【1】,而 head.next 也就是節點【4】了
最後返回交換後的節點【1】
3.4 小補充
還記得上面我們說的,如果連結串列為奇數,最後結果如何呢?
現在接著上面的 debug 看下最後奇數的節點怎麼返回。
假設現在新增一個節點【5】
按照 if 判斷,節點【5】為最後一個節點,進入 if 判斷後就將節點【5】返回。
還記得我們上面說的 head.next 指標嗎,它指向的是遞迴返回的結果,我們最後一次遞迴的時候,head 不就是節點【3】嗎!
當節點【3】和節點【4】交換後,節點【3】不就正好指向了返回的節點【5】
四,總結
解決連結串列相關的題目,我們大多可以使用雙指標(快慢指標),陣列,遞迴,迭代這 4 種方式。
在做完簡單題目後,再加上本篇文章的 3 道中等題目,使用雙指標,遞迴就可解決大多數的題目。後面將中等題目刷完後,再來看看連結串列題目有多少是可以用上述幾種方式去解決。
最後,求關注
原創不易,每一篇都是用心在寫。如果對您有幫助,就請一鍵三連(關注,點贊,再轉發)
我是楊小鑫,堅持寫作,分享更多有意義的文章。
感謝您的閱讀,期待與您相識!