碎碎念
链表的反转,可谓在面试中的高频题目了,经常会被问到。像微软,Facebook,amazon,Adobe等大公司的面试或笔试题,也都会出现这个题目。今天又被问到了,OMG~然而我不怕。看完今天这篇文章,相信你们也能在面试官面前帅气的写下这个算法了。
实现链表的反转,无非以下两种思路:
-
思路一:遍历一遍链表,依次改变指针的指向。
时间上,只将整个链表遍历了一遍,所以时间复杂度是O(n)级别的;
空间上,会开辟三个指针的空间,所以空间复杂度是O(1)级别的。 -
思路二:遍历原来的链表,将元素依次放入栈中,再一个个弹出。这时再遍历一遍链表,用弹出栈顶的元素直接更新给链表节点的值。
效率低,且开辟了额外的空间。最重要一点,它改变了链表中节点的值,而通常情况下,改变链表值是不允许的。所以这种思路不提倡。
下面就来具体介绍一下如何帅气地实现链表的反转。
链表节点的数据结构
- 一个数据域
- 一个指向下个节点的next域(可以理解为C++中的next指针)
/**
* 链表中的节点
*/
private static class ListNode {
/**
* 节点的值
*/
private int val;
/**
* 节点的指针域,指向下一个节点
*/
private ListNode next;
/**
* 构造函数
*
* @param value
*/
public ListNode(int value) {
val = value;
}
}
链表反转实现
非递归
复杂度分析
- 只将整个链表遍历了一遍,时间复杂度是O(n)级别的
- 空间复杂度上,因为开了三个指针的空间,所以是O(1)
/**
* 实现链表反转(非递归方式 - 修改指针)
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*
* @param head
* @return
*/
public static ListNode reverse(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while (cur != null) {
// 保存next指针,指向当前节点的下一个节点
ListNode next = cur.next;
// 当前节点指针反转,指向它的前一个节点
cur.next = pre;
// pre指向cur
pre = cur;
// cur指向next
cur = next;
}
return pre;
}
递归
递归,算的上是一种优雅的写法了~
/**
* 实现链表反转(递归方式)
*
* @param node
* @return
*/
public static ListNode reverseRecursion(ListNode node) {
if (node.next == null || node == null) {
return node;
}
ListNode tempNode = reverse(node.next);
node.next.next = node;
node.next = null;
return tempNode;
}
链表的创建
非递归
/**
* 通过数组创建链表(非递归方式)
*
* @param arr
* @return
*/
public static ListNode createLinkedList(int[] arr) {
if (null == arr || arr.length == 0) {
return null;
}
// 创建头节点
ListNode head = new ListNode(arr[0]);
// 当前节点curNode指向head
ListNode curNode = head;
for (int i = 1; i < arr.length; i++) {
// 当前节点的next域指向新创建的节点
curNode.next = new ListNode(arr[i]);
// 当前节点向后移
curNode = curNode.next;
}
// 返回链表的头节点
return head;
}
递归
/**
* 通过数组创建链表(递归方式)
*
* @param arr
* @return
*/
public static ListNode createLinkedListRecursion(int[] arr) {
if (null == arr || arr.length == 0) {
return null;
}
ListNode head = new ListNode(arr[0]);
ListNode tempNode = createLinkedList(ArrayUtil.sub(arr, 1, arr.length));
head.next = tempNode;
// 返回链表的头节点
return head;
}
Tips:
数组的截取,这里用到了ArrayUtil工具类,它是hutool里的一个工具类。hutool,是一个小而全的Java类库,谐音“糊涂”,然而使用起来可一点不糊涂,可谓真正实现了让Java语言也可以小甜甜(好冷~不过真的是这样)。感兴趣的可以使用一下,如果是Maven程序的话,可以直接在pom文件里添加这个依赖。
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.1</version>
</dependency>
链表的打印
/**
* 打印链表
*
* @param head
*/
public static void printLinkedList(ListNode head) {
ListNode curNode = head;
while (curNode != null) {
// 打印当前节点
System.out.print(curNode.val + " -> ");
// 当前节点向后移
curNode = curNode.next;
}
System.out.println("NULL");
}
完整代码
package basic.linkedlist;
import cn.hutool.core.util.ArrayUtil;
/**
* @Description:链表反转
* @Author: during
* @Date: 2020/4/21
*/
public class ReverseLinkedList {
/**
* 链表中的节点
*/
private static class ListNode {
/**
* 节点的值
*/
private int val;
/**
* 节点的指针域,指向下一个节点
*/
private ListNode next;
/**
* 构造函数
*
* @param value
*/
public ListNode(int value) {
val = value;
}
}
/**
* 通过数组创建链表(非递归方式)
*
* @param arr
* @return
*/
public static ListNode createLinkedList(int[] arr) {
if (null == arr || arr.length == 0) {
return null;
}
// 创建头节点
ListNode head = new ListNode(arr[0]);
// 当前节点curNode指向head
ListNode curNode = head;
for (int i = 1; i < arr.length; i++) {
// 当前节点的next域指向新创建的节点
curNode.next = new ListNode(arr[i]);
// 当前节点向后移
curNode = curNode.next;
}
// 返回链表的头节点
return head;
}
/**
* 通过数组创建链表(递归方式)
*
* @param arr
* @return
*/
public static ListNode createLinkedListRecursion(int[] arr) {
if (null == arr || arr.length == 0) {
return null;
}
ListNode head = new ListNode(arr[0]);
ListNode tempNode = createLinkedList(ArrayUtil.sub(arr, 1, arr.length));
head.next = tempNode;
// 返回链表的头节点
return head;
}
/**
* 打印链表
*
* @param head
*/
public static void printLinkedList(ListNode head) {
ListNode curNode = head;
while (curNode != null) {
// 打印当前节点
System.out.print(curNode.val + " -> ");
// 当前节点向后移
curNode = curNode.next;
}
System.out.println("NULL");
}
/**
* 实现链表反转(非递归方式 - 修改指针)
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*
* @param head
* @return
*/
public static ListNode reverse(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while (cur != null) {
// 保存next指针,为当前节点的next域
ListNode next = cur.next;
// 当前节点指针反转,指向它的前一个节点
cur.next = pre;
// pre指向cur
pre = cur;
// cur指向next
cur = next;
}
return pre;
}
/**
* 实现链表反转(递归方式)
*
* @param node
* @return
*/
public static ListNode reverseRecursion(ListNode node) {
if (node.next == null || node == null) {
return node;
}
ListNode tempNode = reverse(node.next);
node.next.next = node;
node.next = null;
return tempNode;
}
/**
* 测试
*
* @param args
*/
public static void main(String[] args) {
int count = 100;
int[] arr = new int[count];
for(int i = 0; i< count; i++) {
arr[i] = i;
}
ListNode head = createLinkedList(arr);
/*ListNode head = createLinkedListRecursion(arr);*/
printLinkedList(head);
ListNode reverseHead = reverse(head);
/*ListNode reverseHead = reverseRecursion(head);*/
printLinkedList(reverseHead);
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.during</groupId>
<artifactId>algorithm</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
</project>