目录
0. 引言
abandon!abandon!
这道题和1. 两数之和 并称为算法届的"abandon"!。
今天水水内容, 就从梦开始的地方出发吧。
反转链表做了之后, 可以尝试做92. 反转链表 II , 也是懒猪博主下一篇更新内容。
此题如此简单, 那我的任务就是让每一个看了的人都能完全看懂,并且铭记于心,因为很多人总是尝试想学好算法, 就从反转链表和两数之和做起, 但是三天打鱼两天晒网, 忙其他事情去了, 过段时间又想学算法, 又从abandon开始, 不如一步到胃, 就再仔细看这一次吧!
1. 题目介绍

2. 思路
2.1 更清晰的示例
题目看似是1->2->3->4->5, 其实最后一个节点5还会往后面指向null的, 这也是判断它是否为链尾节点的依据。

同样地, 反转后的也是。由于是在原链表上面直接操作, 因此我们直接这样看吧:

2.2 确定变量
2.2.1 前驱节点pre
首先, 我们发现, 节点1最终是要当链尾节点的, 因此它会指向null, 而且是它率先做出改变, 第一步就操作节点1,因此可以考虑迭代式地从左往右进行操作。
要将一个节点, 比如2指向它的前一个节点1, 那么肯定要记录它的前一个节点吧, 因此会有变量pre记录其前驱节点。
节点1要指向null, 那么前驱节点会初始化为null:
ListNode pre = null;
2.2.2 后继节点next
对于一个单向链表, 某个节点想要链接上另一个节点, 靠的是.next = 另一个节点来更新。
对于节点1, 如果先要将它指向pre, 那么它的next就给了pre, 它跟节点2就分手了, 没有联系了。
那么它怎么回来呢?那我问你, 玩过阿卡丽吗?
阿卡丽的E技能是:


如图所示, 现在这里有4个节点, 你作为阿卡丽[2]想要拿人头[3]并安全返回烟幕[1]里面, 请先往你原来的地方[1]放回旋镖。
意思就是, 当节点1要把它的next指向前驱节点pre之前, 请先记录好它要回去的地方, 记录好它原先的next指向的节点2。
// 先记录节点的原先的后面一个节点
ListNode next = cur.next;
// 然后再将next指针指向左边的前驱节点
cur.next = pre;


2.3 往后迭代循环移动
此时, pre取代cur的位置, 现在节点1作为pre了,
而节点2作为当前要操作的节点cur, 它变成了阿卡丽!
pre = cur;
cur = next;

接着将会继续循环, 判断条件是 当前的节点cur不能为空。
记得在最开始cur初始化为head, 你问为什么head不直接作为cur, 你见过领导干活吗, head是你的头头, 它不可替代。而你作为牛马cur, 你一直在变化, 用完了也没人管你, 直接扔掉。
// 找个外包cur来干活, 领导head去睡觉
ListNode cur = head;
// 要遍历的当前节点cur不为空 则继续
while (cur != null) {
ListNode next = cur.next;
// 5. 将cur的next指针指向前驱节点, 实现一次反转
cur.next = pre;
// 6. 反转后, pre向右移动, 取代cur的位置
pre = cur;
// 7. 由于第4步已经提前记录好了next节点, 赋值给cur即可, 实现cur的向右移动
cur = next;
}
2.4 循环结束, 返回结果
此时外包小子cur为空了, 循环结束, 很明显现在前驱节点pre成为这个链表的头节点, 直接返回。
return pre;
3. 打印示例
你是否为构造链表或者树对象感到麻烦,
你是否想清晰看懂对象结构,
请看我的另一篇分享博客:打印节点(链表或者二叉树)工具-优快云博客
public static void main(String[] args) {
// 获取打印工具对象, 第二个参数为0代表是链表
PrintUtils utils = new PrintUtils(new int[]{1, 2, 3, 4, 5}, 0);
// 打印出来链表
utils.print();
// 获取链表结构, 作为本题形参
ListNode head = reverseList(utils.getListNode());
// 使用打印工具打印出链表结构
PrintUtils.printListNode(head);
}
请看控制台:

4. 代码
Java
public static ListNode reverseList(ListNode head) {
// 1. 由于要反转, 第一个节点的前面要为空, 那么就初始化前驱节点pre为空
ListNode pre = null;
// 2. cur来作为遍历节点, 初始化为头节点
// 找个外包cur来干活, 领导head去睡觉
ListNode cur = head;
// 3. 要遍历的当前节点cur不为空 则继续
while (cur != null) {
// 4. 由于要变换节点, 先提前记录好当前节点cur的下一个节点next
ListNode next = cur.next;
// 5. 将cur的next指针指向前驱节点, 实现一次反转
cur.next = pre;
// 6. 反转后, pre向右移动, 取代cur的位置
pre = cur;
// 7. 由于第4步已经提前记录好了next节点, 赋值给cur即可, 实现cur的向右移动
cur = next;
}
// 8. 当前遍历的节点为空, 那么这个链表的最后一个节点则是反转后的第一个节点, cur为空, 前驱节点pre现在就是老大, return!
return pre;
}
public static void main(String[] args) {
// 获取打印工具对象, 第二个参数为0代表是链表
PrintUtils utils = new PrintUtils(new int[]{1, 2, 3, 4, 5}, 0);
// 打印出来链表
utils.print();
// 获取链表结构, 作为本题形参
ListNode head = reverseList(utils.getListNode());
// 使用打印工具打印出链表结构
PrintUtils.printListNode(head);
}
C++
ListNode* reverseList(ListNode* head) {
// 1. 由于要反转, 第一个节点的前面要为空, 那么就初始化前驱节点pre为空
ListNode* pre = nullptr;
// 2. cur来作为遍历节点, 初始化为头节点
// 找个外包cur来干活, 领导head去睡觉
ListNode* cur = head;
// 3. 要遍历的当前节点cur不为空 则继续
while (cur != nullptr) {
// 4. 由于要变换节点, 先提前记录好当前节点cur的下一个节点next
ListNode* next = cur->next;
// 5. 将cur的next指针指向前驱节点, 实现一次反转
cur->next = pre;
// 6. 反转后, pre向右移动, 取代cur的位置
pre = cur;
// 7. 由于第4步已经提前记录好了next节点, 赋值给cur即可, 实现cur的向右移动
cur = next;
}
// 8. 当前遍历的节点为空, 那么这个链表的最后一个节点则是反转后的第一个节点, cur为空, 前驱节点pre现在就是老大, return!
return pre;
}
JS
function reverseList(head) {
// 1. 由于要反转, 第一个节点的前面要为空, 那么就初始化前驱节点pre为空
let pre = null;
// 2. cur来作为遍历节点, 初始化为头节点
// 找个外包cur来干活, 领导head去睡觉
let cur = head;
// 3. 要遍历的当前节点cur不为空 则继续
while (cur != null) {
// 4. 由于要变换节点, 先提前记录好当前节点cur的下一个节点next
let next = cur.next;
// 5. 将cur的next指针指向前驱节点, 实现一次反转
cur.next = pre;
// 6. 反转后, pre向右移动, 取代cur的位置
pre = cur;
// 7. 由于第4步已经提前记录好了next节点, 赋值给cur即可, 实现cur的向右移动
cur = next;
}
// 8. 当前遍历的节点为空, 那么这个链表的最后一个节点则是反转后的第一个节点, cur为空, 前驱节点pre现在就是老大, return!
return pre;
}
5. 一键三连么么哒
宝宝们觉得我的分享笔记有用的话, 点赞关注收藏么么哒!
我的分享笔记都会采取有趣的方式分享,印象深才会记得住。
1万+



被折叠的 条评论
为什么被折叠?



