递归反转链表的一部分
遞歸反轉(zhuǎn)鏈表的一部分
反轉(zhuǎn)單鏈表的迭代實現(xiàn)不是?個困難的事情, 但是遞歸實現(xiàn)就有點難度了,如果再加?點難度, 讓你僅僅反轉(zhuǎn)單鏈表中的?部分, 你是否能夠遞歸實現(xiàn)呢?
本?就來由淺?深, 逐步地解決這個問題。 如果你還不會遞歸地反轉(zhuǎn)單鏈表也沒關(guān)系, 本?會從遞歸反轉(zhuǎn)整個單鏈表開始拓展, 只要你明?單鏈表的結(jié)構(gòu), 相信你能夠有所收獲
// 單鏈表節(jié)點的結(jié)構(gòu) public class ListNode {int val;ListNode next;ListNode(int x) { val = x; } }什么叫反轉(zhuǎn)單鏈表的?部分呢, 就是給你?個索引區(qū)間, 讓你把單鏈表中這部分元素反轉(zhuǎn), 其他部分不變:
迭代的思路?概是: 先??個 for 循環(huán)找到第 m 個位置, 然后再??個 for 循環(huán)將 m 和 n 之間的元素反轉(zhuǎn)。
但是我們的遞歸解法不??個 for 循環(huán), 純遞歸實現(xiàn)反轉(zhuǎn)
?、 遞歸反轉(zhuǎn)整個鏈表
ListNode reverse(ListNode head) {if (head.next == null) return head;ListNode last = reverse(head.next);head.next.next = head;//相當于把head的next個節(jié)點的next指針指向自己//此時head節(jié)點和head的next節(jié)點之間相當于雙向鏈表//斷開head指向next個節(jié)點的指針,因為代碼走到這里說明//head的下一個next節(jié)點的next指針已經(jīng)指向head節(jié)點了//完成反轉(zhuǎn)功能head.next = null;return last; }我們下?來詳細解釋?下這段代碼
對于遞歸算法, 最重要的就是明確遞歸函數(shù)的定義。 具體來說, 我們的reverse 函數(shù)定義是這樣的:輸??個節(jié)點 head , 將「以 head 為起點」 的鏈表反轉(zhuǎn), 并返回反轉(zhuǎn)之后的頭結(jié)點。
明?了函數(shù)的定義, 在來看這個問題。 ?如說我們想反轉(zhuǎn)這個鏈表:
那么輸? reverse(head) 后, 會在這?進?遞歸:
ListNode last = reverse(head.next);不要跳進遞歸(你的腦袋能壓?個棧呀? ) , ?是要根據(jù)剛才的函數(shù)定義,來弄清楚這段代碼會產(chǎn)?什么結(jié)果:
這個 reverse(head.next) 執(zhí)?完成后, 整個鏈表就成了這樣:
并且根據(jù)函數(shù)定義, reverse 函數(shù)會返回反轉(zhuǎn)之后的頭結(jié)點, 我們?變量last 接收了
現(xiàn)在再來看下?的代碼:
head.next.next = head;接下來:
head.next = null; return last;神不神奇, 這樣整個鏈表就反轉(zhuǎn)過來了! 遞歸代碼就是這么簡潔優(yōu)雅, 不過其中有兩個地?需要注意:
1、 遞歸函數(shù)要有 base case, 也就是這句:
if (head.next == null) return head; 意思是如果鏈表只有?個節(jié)點的時候反轉(zhuǎn)也是它??, 直接返回即可。2、 當鏈表遞歸反轉(zhuǎn)之后, 新的頭結(jié)點是 last , ?之前的 head 變成了最后?個節(jié)點, 別忘了鏈表的末尾要指向 null:
head.next = null;理解了這兩點后, 我們就可以進?步深?了, 接下來的問題其實都是在這個
算法上的擴展。
?、 反轉(zhuǎn)鏈表前 N 個節(jié)點
這次我們實現(xiàn)?個這樣的函數(shù):
// 將鏈表的前 n 個節(jié)點反轉(zhuǎn)(n <= 鏈表?度) ListNode reverseN(ListNode head, int n)?如說對于下圖鏈表, 執(zhí)? reverseN(head, 3) :
解決思路和反轉(zhuǎn)整個鏈表差不多, 只要稍加修改即可:
具體的區(qū)別:
1、 base case 變?yōu)?n == 1 , 反轉(zhuǎn)?個元素, 就是它本?, 同時要記錄后驅(qū)
節(jié)點。
2、 剛才我們直接把 head.next 設(shè)置為 null, 因為整個鏈表反轉(zhuǎn)后原來的head 變成了整個鏈表的最后?個節(jié)點。 但現(xiàn)在 head 節(jié)點在遞歸反轉(zhuǎn)之后不?定是最后?個節(jié)點了, 所以要記錄后驅(qū) successor (第 n + 1 個節(jié)點) , 反轉(zhuǎn)之后將 head 連接上。
三、 反轉(zhuǎn)鏈表的?部分
現(xiàn)在解決我們最開始提出的問題, 給?個索引區(qū)間 [m,n] (索引從 1 開始) , 僅僅反轉(zhuǎn)區(qū)間中的鏈表元素。
ListNode reverseBetween(ListNode head, int m, int n)?先, 如果 m == 1 , 就相當于反轉(zhuǎn)鏈表開頭的 n 個元素嘛, 也就是我們剛才實現(xiàn)的功能:
ListNode reverseBetween(ListNode head, int m, int n) {// base caseif (m == 1) {// 相當于反轉(zhuǎn)前 n 個元素return reverseN(head, n);}// ... }如果 m != 1 怎么辦?如果我們把 head 的索引視為 1,那么我們是想從第 m 個元素開始反轉(zhuǎn)對吧;如果把 head.next 的索引視為 1 呢?那么相對于 head.next,反轉(zhuǎn)的區(qū)間應(yīng)該是從第 m - 1 個元素開始的;那么對于 head.next.next 呢……
區(qū)別于迭代思想,這就是遞歸思想,所以我們可以完成代碼:
ListNode reverseBetween(ListNode head, int m, int n) {// base caseif (m == 1) {return reverseN(head, n);}// 前進到反轉(zhuǎn)的起點觸發(fā) base casehead.next = reverseBetween(head.next, m - 1, n - 1);return head; }四、 最后總結(jié)
遞歸的思想相對迭代思想, 稍微有點難以理解, 處理的技巧是: 不要跳進遞歸, ?是利?明確的定義來實現(xiàn)算法邏輯。
遞歸操作鏈表并不?效。 和迭代解法相?, 雖然時間復雜度都是 O(N), 但是迭代解法的空間復雜度是 O(1), ?遞歸解法需要堆棧, 空間復雜度是 O(N)。
總結(jié)
以上是生活随笔為你收集整理的递归反转链表的一部分的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单调队列之滑动窗口
- 下一篇: 队列实现栈 | 栈实现队列