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