对应力扣题目 206. 反转链表 - 力扣(LeetCode)
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
输入:[1,2]
输出:[2,1]
输入:[]
输出:[]
在对于下列的链表算法题:
我们先创建一个通用节点类:
/**
* @BelongsProject: arithmetic
* @BelongsPackage: com.hzp.algorithm.datastructure
* @Author: ASUS
* @CreateTime: 2023-09-17 21:38
* @Description: TODO 通用节点类
* @Version: 1.0
*/
public class ListNode {
public int value;
public ListNode next;
public ListNode(int value, ListNode next) {
this.value = value;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(64);
sb.append("[");
ListNode p = this;
while (p != null) {
sb.append(p.value);
if (p.next != null) {
sb.append(",");
}
p = p.next;
}
sb.append("]");
return sb.toString();
}
//创建新链表并且赋值
public static ListNode of(int... elements) {
if (elements.length == 0) {
return null;
}
ListNode p = null;
for (int i = elements.length - 1; i >= 0; i--) {
p = new ListNode(elements[i], p);
}
return p;
}
}
方法一
构造一个新链表,从旧链表依次拿到每个节点,创建新节点添加至新链表头部,完成后新链表即是倒序的
public ListNode reverseList(ListNode o1) {
ListNode n1 = null;
ListNode p = o1;
while (p != null) {
n1 = new ListNode(p.val, n1);
p = p.next;
}
return n1;
}
评价:简单直白,就是得新创建节点对象
方法二
与方法1 类似,构造一个新链表,从旧链表头部移除节点,添加到新链表头部,完成后新链表即是倒序的,区别在于原题目未提供节点外层的容器类,这里提供一个,另外一个区别是并不去构造新节点
static class List {
ListNode head;
public List(ListNode head) {
this.head = head;
}
public ListNode removeFirst(){
ListNode first = head;
if (first != null) {
head = first.next;
}
return first;
}
public void addFirst(ListNode first) {
first.next = head;
head = first;
}
}
执行代码:
public ListNode reverseList(ListNode head) {
List list1 = new List(head);
List list2 = new List(null);
ListNode first;
while ((first = list1.removeFirst()) != null) {
list2.addFirst(first);
}
return list2.head;
}
评价:更加面向对象,如果实际写代码而非刷题,更多会这么做
方法三(递归)
递归,在归时让 5 --> 4,4 --> 3 ...
首先,写一个递归方法,返回值用来拿到最后一个节点
public ListNode reverseList(ListNode p) {
if (p == null || p.next == null) { // 不足两个节点
return p; // 最后一个节点
}
ListNode last = reverseList(p.next);
return last;
}
注意1:递归终止条件是 curr.next == null,目的是到最后一个节点就结束递归,与之前递归遍历不一样
注意2:需要考虑空链表即 p == null 的情况
可以先测试一下:
ListNode o5 = new ListNode(5, null);
ListNode o4 = new ListNode(4, o5);
ListNode o3 = new ListNode(3, o4);
ListNode o2 = new ListNode(2, o3);
ListNode o1 = new ListNode(1, o2);
ListNode n1 = new E01Leetcode206().reverseList(o1);
System.out.println(n1);
下面为伪码调用过程,假设节点分别是 1 --> 2 --> 3 --> 4 --> 5 --> null,先忽略返回值
reverseList(ListNode p = 1) {
reverseList(ListNode p = 2) {
reverseList(ListNode p = 3) {
reverseList(ListNode p = 4) {
reverseList(ListNode p = 5) {
if (p == null || p.next == null) {
return p; // 返回5
}
}
// 此时p是4, p.next是5
}
// 此时p是3, p.next是4
}
// 此时p是2, p.next是3
}
// 此时p是1, p.next是2
}
接下来,从 p = 4 开始,要让 5 --> 4,4 --> 3 ...
reverseList(ListNode p = 1) {
reverseList(ListNode p = 2) {
reverseList(ListNode p = 3) {
reverseList(ListNode p = 4) {
reverseList(ListNode p = 5) {
if (p == null || p.next == null) {
return p; // 返回5
}
}
// 此时p是4, p.next是5, 要让5指向4,代码写成 p.next.next=p
// 还要注意4要指向 null, 否则就死链了
}
// 此时p是3, p.next是4
}
// 此时p是2, p.next是3
}
// 此时p是1, p.next是2
}
最终代码为:
public ListNode reverseList(ListNode p) {
if (p == null || p.next == null) { // 不足两个节点
return p; // 最后一个节点
}
ListNode last = reverseList(p.next);
p.next.next = p;
p.next = null;
return last;
}
Q:为啥不能在递的过程中倒序?
A:比如
-
1 \rightarrow 2 \rightarrow 3 如果递的过程中让 2 \rightarrow 1 那么此时 2 \rightarrow 3 就被覆盖,不知道接下来递给谁
-
而归的时候让 3 \rightarrow 2 不会影响上一层的 1 \rightarrow 2
评价:单向链表没有 prev 指针,但利用递归的特性【记住了】链表每次调用时相邻两个节点是谁
方法四
从链表每次拿到第二个节点,将其从链表断开,插入头部,直至它为 null 结束
对应的图示:
参考代码:
//方法4
public ListNode reverseList(ListNode o1){
//对于特殊情况的处理 1.空链表 2.一个元素
if(o1==null ||o1.next==null){
return o1;
}
ListNode o2=o1.next;
ListNode n1=o1;
while(o2!=null){
o1.next=o2.next; //2.
o2.next=n1; //3.
n1=o2; //4.
o2=o1.next; //5.
}
return n1;
}
方法五
要点:把链表分成两部分,思路就是不断从链表2的头,往链表1的头搬移
参考代码:
//方法5
public ListNode reverseList(ListNode o1){
//对于特殊情况的处理 1.空链表 2.一个元素
if(o1==null || o1.next==null){
return o1;
}
ListNode n1=null;
while (o1!=null){
ListNode o2=o1.next; //2.
o1.next=n1; //3.
n1=o1; //4.
o1=o2; //4.
}
return n1;
}
评价:本质上与方法2 相同,只是方法2更为面向对象