前言:
大家好,今天是LeetCode每日一题的第八天,,给大家分享的是链表反转,难度系数两颗星!废话不多说,咱先上题目!
1.1 题目要求
题目类型: 链表反转
题目内容: 将单链表的链接顺序反转过来
测试案例:
输入: 1->2->3->4->5
输出: 5->4->3->2->1
注意事项: 使用两种方式解题!
1.2 解题方法
上面提到了实现链表反转,使用两种方式进行解题,那这两种方式是什么呢?让我们接着往下看!
1.2.1 使用迭代法
1.解题思路
核心思想:重复某一过程, 每一次处理结果作为下一次处理的初始值,这些初始值类似于状态, 每次处理都会改变状态, 直至到达最终状态
大致思路:
- 从前往后的遍历链表, 将当前节点的next指向上一个节点, 因此需要一个变量prev存储上一个节点;
- 当前节点处理完后需要寻找下一个节点, 因此需要一个变量curr保存当前节点;
- 处理完后要将curr(当前节点)赋值给prev(上一节点), 并将next指针赋值给curr(当前节点),因此需要一个临时变量提前保存下一个节点的next指针
链表反转处理过程:
- 将curr(当前节点)的next指针保存到next临时变量, 即 next = curr.next
- 将prev(上一节点)赋值给curr(当前节点)的next指针, 即 curr.next = prev
- 本轮处理的curr(当前节点)在下一轮节点处理中变成了prev(上一节点),即prev = curr;
- 本轮处理结束后, 将next(临时变量)赋值给下一轮节点处理的curr(当前节点),即 curr = next;
1.2.2 代码实现
1.初次编写
- 测试代码
package com.kuang.leetcode1;
/**
* @ClassName ReverseList1
* @Description 链表反转-使用迭代方式
* @Author 狂奔の蜗牛rz
* @Date 2021/8/26
*/
public class ReverseList1 {
public static void main(String[] args) {
//初始化5个链表节点, 指定val当前值和next指向节点
//节点5
ListNode node5 = new ListNode(5, null);
//节点4
ListNode node4 = new ListNode(4, node5);
//节点3
ListNode node3 = new ListNode(3, node4);
//节点2
ListNode node2 = new ListNode(2, node3);
//节点1
ListNode node1 = new ListNode(1, node2);
//使用迭代方式进行链表反转(头结点为node1)
ListNode prev = iterate(node1);
//打印prev节点值
System.out.println(prev);
}
//静态内部类ListNode(链表节点)
static class ListNode {
int val; //当前节点的对应值
ListNode next; //next指针, 指向下一个链表节点
/**
* ListNode有参构造函数
* @param val 当前节点值
* @param next 下一链表节点
*/
public ListNode(int val, ListNode next) {
//初始化当前值
this.val = val;
//初始化next节点
this.next = next;
}
}
/**
* 迭代反转链表元素
* @param head 链表头结点
* @return ListNode 链表节点
*/
public static ListNode iterate(ListNode head) {
//prev为链表中上一个节点(1是头结点, 它的上一节点值为null), next为存储下一个节点的临时变量
ListNode prev = null, next;
//curr是指当前节点(头结点)
ListNode curr = head;
/**
* 这里涉及到要进行迭代(或者循环遍历), 那么是使用for循环还是while循环呢?
* 由于链表的长度未知, 因此无法使用for循环来进行有限次循环,
* 当前迭代方法只有一个head参数, 只包含head头结点的值和next指针,
* 并不包含迭代器, 而for循环底层是依赖于迭代器的, 因此采用while循环
*/
/**
* 那么while循环的执行条件是什么呢?
* curr(当前节点)值为null时, 说明执行到了链表的最后一个元素,
* 只要curr值不为null,证明还需要继续执行循环
* 因此while循环的执行条件为当前节点值不为null(即curr != null)
*/
while (curr != null) {
//将curr(当前节点, 值为1)的next(下一节点, 值为2)赋值给next(临时变量)
next = curr.next;
//将prev(上一节点, 初值为null)赋值给curr(当前节点, 值为1)的next(下一节点)
curr.next = prev;
//将pre和curr重新赋值, 为处理下一个节点做准备
//本轮处理的curr(当前节点, 值为1)在下一轮节点处理中变成了prev(上一节点)
prev = curr;
//本轮处理结束后, 将next(临时变量, 值为2)赋值给下一轮节点处理的curr(当前节点)
curr = next;
}
//将prev变量(此时该变量的值为上一轮循环中的当前节点)返回
return prev;
}
}
注意:如果编写算法题时没有思路,可以先根据题干编写出一个大致的代码框架, 然后在仔细分析题干和进一步调试,将代码补充完成
-
Debug测试
将主方法中的最后两行代码打上断点,然后依次进行Debug测试
- 测试结果
链表反转前:
链表反转后:
结果:链表成功实现反转!
1.2.2 使用递归法
1.解题思路
核心思想:
-
以相似的方法重复, 类似于树结构, 先从根节点找到叶子节点, 从叶子节点开始遍历;
-
把大的问题(整个链表)拆成性质相同的小问题(两个元素反转), 即curr.next.next = curr,
将所有的小问题解决后, 大的问题也就解决了
问题思考:
假设1为head头结点, 1的next值是2, 2的next值为3; 如果要实现链表反转, 需要把指向3的next指针断掉, 让2的next指针指向1,怎样实现呢?
解决方案:
因此需要设置一个next临时变量来存储head.next(值为2), 即head.next.next = head = 1;
但2的next指针指向1后, 1的next指针也指向了2, 这样会形成一个环形链表,怎样解决?
解决方案:
让头结点1的next指针指向null, 即head.next = null
同理, 如果从链表尾部开始, 当前节点5的next指针指向4, 4的next指针指向null, 但是3的next指针并没有断开, 仍然指向4, 这样就可以确定4的位置
注意要点:
- 只需每个元素都执行curr.next.next = curr, curr.next = null 两个步骤即可
- 为了保证链表不断开, 必须从最后一个元素开始
2.代码实现
- 测试代码
package com.kuang.leetcode1;
/**
* @ClassName ReverseList1
* @Description 链表反转-使用递归方式
* @Author 狂奔の蜗牛rz
* @Date 2021/8/26
*/
public class ReverseList2 {
public static void main(String[] args) {
//初始化5个链表节点, 指定val当前值和next指向节点
//节点5
ListNode node5 = new ListNode(5, null);
//节点4
ListNode node4 = new ListNode(4, node5);
//节点3
ListNode node3 = new ListNode(3, node4);
//节点2
ListNode node2 = new ListNode(2, node3);
//节点1
ListNode node1 = new ListNode(1, node2);
//使用递归方式进行链表反转(头结点为node1)
ListNode prev = recursion(node1);
//打印prev节点值
System.out.println(prev);
}
//内部类ListNode
static class ListNode {
int val; //当前节点值
ListNode next; //next指针, 指向下一节点
/**
* ListNode有参构造函数
* @param val 当前节点值
* @param next 下一链表节点
*/
public ListNode(int val, ListNode next) {
//初始化当前值和next节点
this.val = val;
this.next = next;
}
}
/**
* 递归反转链表元素
* @param head 链表头结点
* @return ListNode 链表节点
*/
public static ListNode recursion(ListNode head) {
/**
* 问题1: 递归停止的条件是什么?
* 如果head(头结点)值为null(说明链表为空, 因此无需反转),
* 或者head(头结点)的next(下一节点)值为null(说明递归到了最后一个元素)
*/
if(head == null || head.next == null) {
//最后将head(头结点)进行返回
return head;
/**
* 问题2:为什么要将head头结点返回呢?
* 如果head头结点为null, 说明当前链表为空, 无需进行反转;
* 如果head(头结点)的next(下一节点)值为null, 说明已经递归到了最后一个元素,所以将当前head结点返回
*/
}
//递归就是自己调用自己
//假设head节点值为4, 那么head.next节点值便是5, 进行递归后, 返回的new_head节点值也为5
ListNode new_head = recursion(head.next);
//head(头结点)的next指针指向的next(下一节点)为头结点
head.next.next = head;
//head(头结点)的next(下一节点)值为null
head.next = null;
//将新建头结点进行返回
return new_head;
/**
* 问题3:最后的返回值是什么?
* 当第五个节点(值为5)进行递归时, 它的next(下一节点)值为null,
* 因此就满足了递归的停止条件(head.next=null), 不再继续执行;
* 链表反转结束后, 还需要将head头结点(此时值为5)进行返回
*/
}
}
-
Debug测试
将主方法中的最后两行代码打上断点,然后依次进行Debug测试
- 测试结果
链表反转前:
链表反转后:
结果:链表成功实现反转!
好了,今天LeetCode每日一题—链表反转到这里就结束了,欢迎大家学习和讨论,点赞和收藏!
参考视频链接:https://www.bilibili.com/video/BV1Ey4y1x7J3 (国内算法宝典-LeetCode算法50讲)