問題:定義一個函數,輸入一個鏈表的頭節點,反轉該鏈表並輸出反轉後鏈表的頭節點。java
示例:算法
輸入: 1->2->3->4->5->NULL 輸出: 5->4->3->2->1->NULL
//單鏈表的實現結構 public class ListNode { int val; ListNode next; ListNode(int x) { val = x;} }
反轉鏈表利用迭代不難實現,若是使用遞歸則有些許難度。函數
首先來看源碼實現:code
ListNode reverse(ListNode head) { if(head == null || head.next == null) return head; ListNode ret = reverse(head.next); head.next.next = head; head.next = null; return ret; }
是否看起來不知所云,而又被這如此簡潔的代碼所震撼?讓咱們一塊兒探索一下其中的奧祕。blog
對於遞歸算法,最重要的是明確遞歸函數的定義。遞歸
咱們的reverse
函數的定義以下:索引
輸入一個節點head
,將以head
爲起點的鏈表反轉,並返回反轉以後的頭節點。源碼
明白了函數的定義後,在來看這個問題。好比咱們想反轉這個鏈表class
那麼輸入reverse(head)
後,會在ListNode ret = reverse(head.next);
進行遞歸變量
不要跳進遞歸!(你的腦殼能壓幾個棧呀?)
根據reverse
函數的定義,函數調用後會返回反轉以後的頭節點,咱們用變量ret
接收
如今再來看一下代碼
head.next.next = head;
接下來:
head.next = null; return ret;
再跳出這層遞歸就會獲得:
神不神奇,這樣整個鏈表就反轉過來了!
遞歸代碼就是這麼簡潔優雅,但要注意兩個問題:
一、遞歸函數要有base case,否則就會一直遞歸,致使棧溢出
if (head == null || head.next == null) return head;
即鏈表爲空或只有一個節點,直接返回
二、當鏈表遞歸反轉後,新的頭節點爲ret
,而head
變成了最後一個節點,應該令鏈表的某尾指向null
head.next = null;
理解這兩個問題以後,咱們能夠進一步深刻研究鏈表反轉的問題,接下來的問題其實均爲在這個算法上的擴展。
接下來咱們來看這個問題:
問題:反轉鏈表前N個節點,並返回鏈表頭節點
說明:1 <= N <= 鏈表長度
示例:
輸入: 1->2->3->4->5->NULL, n = 4 輸出: 4->3->2->1->5->NULL
解決思路和反轉整個鏈表差很少,只需稍加修改
ListNode successor = null; // 後驅節點(第 n + 1 個節點) ListNdoe reverseN(ListNode head, int n) { if (n == 1) { successor = head.next; return head; } // 以 head.next 爲起點,須要反轉前 n - 1 個節點 ListNode ret = reverseN(head.next, n - 1); head.next.next = head; head.next = successor; // 將反轉後的 head 與後面節點鏈接 return ret; }
具體區別:
一、base case 變爲n == 1
, 同時須要記錄後驅節點。
二、以前把head.next
設置爲null
,由於整個鏈表反轉後,head
變爲最後一個節點。
如今head
節點在遞歸反轉後不必定爲最後一個節點,故應記錄後驅successor
(第 n + 1 個節點), 反轉以後將head
鏈接上。
OK,若是這個函數你也能看懂,就離實現反轉一部分鏈表不遠了。
如今咱們開始解決這個問題,給一個索引區間[m, n]
(索引從1開始),僅僅反轉區間中的鏈表元素。
說明:1 <= m <= n <= 鏈表長度
示例:
輸入: 1->2->3->4->5->NULL, m = 2, n = 4 輸出: 1->4->3->2->5->NULL
猛一看很難想到思路。
試想一下,若是m == 1
,就至關於反轉鏈表的前 n 元素嘛,也就是咱們剛纔實現的功能:
ListNode reverseBetween(ListNode head, int m, int n) { //base case if (m == 1) { return reverseN(head, n); // 至關於反轉前 n 個元素 } // ... }
那若是m != 1
該怎麼辦?
若是把head
的索引視爲1
,那麼咱們是想從第m
個元素開始反轉;
若是把head.next
的索引視爲1
,那麼咱們是想從第m - 1
個元素開始反轉;
若是把head.next.next
的索引視爲1
,那麼咱們是想從第m - 2
個元素開始反轉;
......
區別於迭代思想,這就是遞歸的思想,因此咱們能夠完成代碼:
ListNode reverseBetween(ListNode head, int m, int n) { // base case if (m == 1) { return reverseN(head, n); } // 遞歸前進到觸發 base case (m == 1) head.next = reverseBetween(head.next, m - 1, n - 1); return head; }
至此,咱們終於幹掉了大BOSS!