中国建设银行 官方网站桌面百度
链表是一种常见的数据结构,广泛应用于各种算法和程序设计中。反转链表是一个经典的面试题,也是理解递归和链表操作的好例子。本文将详细介绍如何使用递归方法在Java中反转链表,并逐步解析其背后的原理。
链表介绍
链表由一系列节点组成,每个节点包含两个部分:
-
数据域:存储节点的值。
-
指针域:指向下一个节点的引用。
在Java中,链表节点一般用以下类表示:
public class ListNode {int val;ListNode next;public ListNode(int val) {this.val = val;}
}
递归介绍
递归是一种通过函数调用自身来解决问题的方法。它将复杂问题分解为更小的子问题,直到子问题可以直接解决为止。每次递归调用都会在栈中创建一个新的栈帧,保存当前状态。递归结束时,栈帧依次弹出,恢复到上一层调用。
递归的基本要素:
-
基线条件(Base Case):递归终止的条件,防止无限递归。
-
递归条件(Recursive Case):将问题分解为更小的子问题,并调用自身。
反转链表
反转链表通常由迭代和递归两种方法,迭代比较简单、直接。递归实现代码就比较简洁。
递归反转链表的基本思路是将当前节点的next指针指向前一个节点,然后递归地对下一个节点进行同样的操作。递归的核心思想是将问题分解为更小的子问题,直到达到基本情况(通常是链表末尾)。
我们分以下三种常用情况进行示例。
递归反转整个链表
实现代码如下:
/*** 反转整个链表*/
public class ReverseAll {/*** 递归实现* @param head* @return*/public static ListNode reverseRec(ListNode head) {// 基本情况:链表为空或只有一个节点if (head == null || head.next == null) {return head;}// 递归反转剩余部分ListNode last = reverseRec(head.next);// 将当前节点的下一个节点的next指针指向当前节点head.next.next = head;// 将当前节点的next指针置为nullhead.next = null;return last;}/*** 迭代实现* @param head* @return*/public static ListNode reverseIte(ListNode head) {// 链表为空或只有一个节点,则返回该节点if (head == null || head.next == null) {return head;}ListNode pre = null;ListNode cur = head;// 遍历链表while (cur != null) {// 记录当前节点的下一个节点ListNode next = cur.next;// 反转当前节点的指针cur.next = pre;// 移动prev到当前节点pre = cur;// 移动curr到下一个节点cur = next;}return pre;}public static void main(String[] args) {ListNode head1 = new ListNode(1);ListNode head2 = new ListNode(2);ListNode head3 = new ListNode(3);ListNode head4 = new ListNode(4);head1.next = head2;head2.next = head3;head3.next = head4;head4.next = null;ListNode reverse = reverseRec(head1);//ListNode reverse = reverseIte(head1);while (reverse != null) {System.out.println(reverse.val);reverse = reverse.next;}}
}
其核心思想是递归找到链表的最后一个节点,利用递归结束栈帧依次弹出,恢复到上一层调用的特性,将链表从后一次将指针指向前一个元素,实现链表的反转。
反转链表前N个节点
实现反转链表的前N个节点,在上一个实现的基础上将基线条件变为 n==1,同时需要记录后驱节点successor,并将原来head的后驱节点设置为successor;
实现代码如下:
/*** 反转链表的前N个节点*/
public class ReverseFront {/*** 递归实现* @param head* @return*///记录不需要反转的头节点public static ListNode successor=null;public static ListNode reverseRec(ListNode head,int n) {// 记录后半部分的起始节点,并返回新的头节点if (n == 1) {successor = head.next;return head;}// 以head.next为起点,反转前n-1个节点(每多一次递归,则反转的节点少一个)ListNode last = reverseRec(head.next,n-1);// 将当前节点的下一个节点的next指针指向当前节点head.next.next = head;// 将当前节点的next指针置为后驱节点head.next = successor;return last;}/*** 迭代实现* @param head* @return*/public static ListNode reverseIte(ListNode head,int n) {// 链表为空或只有一个节点,则返回该节点if (head == null || head.next == null) {return head;}ListNode pre = null;ListNode cur = head;ListNode successor=null;int i = 1;// 遍历链表while (i <= n) {// 记录当前节点的下一个节点ListNode next = cur.next;// 反转当前节点的指针cur.next = pre;// 移动prev到当前节点pre = cur;// 移动curr到下一个节点cur = next;//记录后半部分的其实节点successor = next;i++;}//将反转之后的前半部分链表的next指针指向后半部分链表head.next = successor;return pre;}public static void main(String[] args) {ListNode head1 = new ListNode(1);ListNode head2 = new ListNode(2);ListNode head3 = new ListNode(3);ListNode head4 = new ListNode(4);head1.next = head2;head2.next = head3;head3.next = head4;head4.next = null;ListNode reverse = reverseRec(head1,2);//ListNode reverse = reverseIte(head1,2);while (reverse != null) {System.out.println(reverse.val);reverse = reverse.next;}}
}
反转链表区间[m,n]中的元素
/*** 反转链表区间[m,n]中的元素*/
public class ReverseSection {/*** 递归实现* @param head* @return*///记录不需要反转的头节点public static ListNode successor=null;public static ListNode reverseRecN(ListNode head,int n) {// 记录后半部分的起始节点,并返回新的头节点if (n == 1) {successor = head.next;return head;}// 以head.next为起点,反转前n-1个节点(每多一次递归,则反转的节点少一个)ListNode last = reverseRecN(head.next,n-1);// 将当前节点的下一个节点的next指针指向当前节点head.next.next = head;// 将当前节点的next指针置为后驱节点head.next = successor;return last;}/*** 递归实现* @param head* @return*/public static ListNode reverseRec(ListNode head,int m,int n) {//m如果==1,则说明需要反转前n个节点if(m == 1){return reverseRecN(head,n);}// 前进到反转的起点触发基线线条件head.next = reverseRec(head.next,m-1,n-1);return head;}/*** 迭代实现* @param head* @return*/public static ListNode reverseIte(ListNode head,int m,int n) {// 链表为空或只有一个节点,则返回该节点if (head == null || head.next == null) {return head;}ListNode pre = null;ListNode cur = head;ListNode nodem1=null;ListNode nodem=null;int i = 1;// 遍历链表while (i <= n ) {// 记录当前节点的下一个节点ListNode next = cur.next;//记录m-1位置的节点if(i == m-1){nodem1=cur;}//记录m位置的节点if(i == m){nodem=cur;}//反转区间中的元素if(i>=m){// 反转当前节点的指针cur.next = pre;}// 移动prev到当前节点pre = cur;// 移动curr到下一个节点cur = next;if(i == n){//将反转区间的第一个元素的指针指向后半部分链表nodem.next = next;//将半部分链表的指针指向反转区间的最后一个元素nodem1.next = pre;}i++;}return head;}public static void main(String[] args) {ListNode head1 = new ListNode(1);ListNode head2 = new ListNode(2);ListNode head3 = new ListNode(3);ListNode head4 = new ListNode(4);head1.next = head2;head2.next = head3;head3.next = head4;head4.next = null;ListNode reverse = reverseRec(head1,2,4);//ListNode reverse = reverseIte(head1,2,4);while (reverse != null) {System.out.println(reverse.val);reverse = reverse.next;}}
}
复杂度分析
- 时间复杂度
不管时递归还是迭代,都只需要循环或者迭代n次就可以完成反转,所以时间复杂度都是O(n),
- 空间复杂度
迭代解法的空间复杂度是O(1),而递归需要堆栈,空间复杂度是O(n);
- 选择建议
如果链表较短且代码简洁性更重要,可以选择递归方法。
如果链表较长或需要优化空间效率,优先选择迭代方法。
总结
递归反转链表是一个经典的算法问题,通过递归方法可以简洁地实现链表的反转。理解递归的关键在于将问题分解为更小的子问题,并正确处理基本情况。希望本文能帮助你更好地理解递归和链表操作,并在实际编程中灵活运用。