反转链表递归迭代
Posted rotk2015
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反转链表递归迭代相关的知识,希望对你有一定的参考价值。
-
讲解的题目包括,LeetCode206、92、 25。
-
首先是经典的LeetCode206,反转整个链表。解法可分为迭代、递归两种方式。而递归解法,根据返回函数的先后顺序(即处理当前递归栈与下一递归栈的先后顺序),又可分为top-down,bottom-up两种方式。本题十分经典,仔细体会,可以加深对迭代、递归的理解。
public ListNode reverseList(ListNode head) { // return iterateSolution(head); // return recursionTopDownSolution(head); return recursionBottomUpSolution(head, null); }
-
迭代解法,实质上是在迭代遍历旧链表的同时,采用头插法建立新链表,这样,新链表自然就是旧链表反转后的结果。那么,考虑一下,每次头插时,都需要同时维护三个变量的信息,故又称三指针法:
private ListNode iterateSolution(ListNode head) { ListNode pre = null, cur = head, nxt; while(cur != null){ nxt = cur.next; cur.next = pre; pre = cur; cur = nxt; } return pre; }
-
先谈点我对递归的个人理解吧。其实递归做的无非就是压栈、出栈。那么,栈有什么特点呢?先入后出。也就是说,如果我们想先处理底层的,再处理顶层的,即,先处理下一步,再处理当前步,那么完全可以使用递归的方式解决。
正如之前所说,递归先进行了一层层的压栈、再反向进行了一层层的出栈。压栈的过程可看作弹簧的展开,而出栈的过程可看作弹簧的收回。那么,如果在弹簧展开的过程中,就将问题解决,这种方式即为bottom-up,自底向上的,也就相当于。已压栈部分的子问题已得到解决,继续压栈只是为了解决剩余部分问题。这种方式,说白了就是迭代。
那么,如果我们把解决问题的时机推后一些呢? 即,在弹簧收回的时候,再解决问题。这就相当于,已压栈部分的子问题并未得到解决,我们要先把问题缓存起来,解决了后边的问题之后,再回过头来得到当前问题的解。这种方式为top-down,自顶向下的。因为在我们解决当前步问题时,可以认为下一步以后的所有问题已经解决完。
-
递归解法:自底向上。可以仔细对比一下,与迭代解法的异同点。此外,注意 return 语句与处理当前步的先后顺序,以及递归终止条件、返回值。
private ListNode recursionBottomUpSolution(ListNode cur, ListNode pre) { if(cur == null) return pre; ListNode nxt = cur.next; cur.next = pre; return recursionBottomUpSolution(nxt, cur); }
-
递归解法:自顶向下。(这里使用的 head.next,next 有”藕断丝连“意)同样,注意 return 语句与处理当前步的先后顺序,以及递归终止条件、返回值。(递归函数内部的head.next = null 可以省略,只需在调用 recursionTopDownSolution 的外部函数里,将 head 实参的 next 修改为 null 即可)
private ListNode recursionTopDownSolution(ListNode head) { if(head.next == null) return head; ListNode newHead = recursionTopDownSolution(head.next); head.next.next = head; head.next = null; return newHead; }
-
然后是LeetCode92,反转部分链表。由于部分反转包括了从一开始就反转的情况,故 dummyHead 十分有用。此处仅列举 top-down 的递归解法以及迭代解法。
两者思路是一致的,由于是部分反转,因此我们要先遍历至左边界(left)的前一个节点(因为是单向链表,回头很麻烦),然后剩下的就是反转有限长度的链表问题,那么,只需在LeetCode206基础上,加个计数循环判定即可。
public ListNode reverseBetween(ListNode head, int left, int right) { return recursionSolution(head, left, right); // return iterateSolution(head, left, right); }
-
迭代解法:
private ListNode iterateSolution(ListNode head, int left, int right) { // one-pass ListNode dummyHead = new ListNode(0); // dummyHead is useful when left = 1 dummyHead.next = head; ListNode lefTail = dummyHead; for(int i=1; i<left; i++) lefTail = lefTail.next; ListNode midTail = lefTail.next; ListNode rigHead = midTail.next; while(right-- > left){ midTail.next = rigHead.next; rigHead.next = lefTail.next; lefTail.next = rigHead; rigHead = midTail.next; } return dummyHead.next; }
-
递归top-down解法:这里要注意的是,由于是部分反转,故对于可能存在的右边界节点的下一个节点,要用已反转的部分链表的尾结点的 next 保存其信息。
private ListNode recursionSolution(ListNode head, int left, int right) { // reverseBottomUp can be totally one-pass // reverseTopDown is one-pass + (right-left) ListNode dummyHead = new ListNode(0); dummyHead.next = head; ListNode lefTail = dummyHead; for(int i=1; i<left; i++) lefTail = lefTail.next; lefTail.next = reverseTopDown(lefTail.next, right-left); return dummyHead.next; } private ListNode reverseTopDown(ListNode head, int res){ // the reverseBottomUp is the same as iterateSolution if(res == 0) return head; ListNode newHead = reverseTopDown(head.next, --res); ListNode tmp = head.next.next; head.next.next = head; head.next = tmp; return newHead; }
-
接着是LeetCode25,每k个一组反转链表,本题又可看作是在LeetCode92的基础上,增加了点限制条件演化而来。由于题目要求,必须满足k个才能反转,故每次要预先遍历试探,判断是否满足k个。
-
这里先介绍一个核心函数 reverseList,基本作用是,头插法反转链表,并返回反转后新链表的头。
private ListNode reverseList(ListNode head, ListNode nxtHead){ // | head...| nxtHead... ListNode tmp, base; base = nxtHead; while(head != nxtHead){ tmp = head.next; head.next = base; base = head; head = tmp; } return base; }
-
递归解法:recurSolution 提交了下一层的新头,而 head.next 的赋值,又将当前层与下一层相连接(需知head在反转后的位置已到了尾部)(其实,该赋值语句功能与reverseList的功能有重合——因为头插是在nxtHead基础上开始的,而此处列出仅仅是为了保证当不足k个无法进行反转链表时,逻辑的正常性)。
private ListNode recurSolution(ListNode head, int k){ // T:O(2n) cause we have to make sure there are k nodes ListNode cur = head; int i = 0; while(i++ < k){ if(cur == null) return head; cur = cur.next; // what if kth is NULL ? } // | head......newHead | cur... ListNode newHead = reverseList(head, cur); head.next = recurSolution(cur, k); return newHead; }
-
迭代解法:
private ListNode iterateSolution(ListNode head, int k){ // still 2-pass ListNode dummyHead = new ListNode(0); dummyHead.next = head; ListNode lefTail = dummyHead; ListNode cur = head; while(head != null){ int i = 0; while(i++ < k){ if(cur == null) return dummyHead.next; cur = cur.next; } lefTail.next = reverseList(head, cur); lefTail = head; head = cur; } return dummyHead.next; }
-
无论是列出的迭代解或是递归解,都需要先探明是否满足k个节点,然而,完全可以使用另一种更大胆的方式,即直接进行k个节点的反转,而当反转过程中发现不够k个节点时,回头再反转一次即可。这种方式的时间复杂度相对更低
以上是关于反转链表递归迭代的主要内容,如果未能解决你的问题,请参考以下文章