一、反转链表(简单)
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
来源: Leecode - 反转链表
分析
链表是一种线性表,它不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针。每个元素实际上是一个单独的对象,所有的对象都是通过每个元素中的引用字段链接在一起。考虑需要反转链表,那么只需要将当前指针的 next 指向上一个节点,上一个节点的 next 指向当前节点,便可以反转链表。对于头结点来说,没有上一个节点,需要我们定义一个空节点来表示上一个节点,分析图如下:
代码实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function (head) {
// 定义上一个节点,用于承载链表头结点反转使用
let pre = null;
// 定义当前节点指向头结点
let cur = head;
// 遍历链表
while (cur) {
// 储存下一个节点
const next = cur.next;
// 当前节点的next指向上一个节点
cur.next = pre;
// 上一个节点指向当前节点
pre = cur;
// 继续下一个节点遍历
cur = next;
}
// 返回反转后的链表
return pre;
};
二、反转链表 - 两两交换链表中的节点(中等难度)
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3] 示例 2:
输入:head = [] 输出:[] 示例 3:
输入:head = [1] 输出:[1]
分析
从上图中知道,需要两两交换节点,并要求在原链表上操作,分解步骤如下:
- 创建一个空节点 dummy,dummy.next 指向 head,用于 head 节点改变后能找到头结点;
- 遍历链表,一组节点的交换需要三个步骤:
当前节点的 next 节点指向下一个节点的 next; 下一个节点的 next 指向当前节点; 上一个节点 pre 的 next 指向下一个节点;
- 更新 pre 节点指针为 head,即目前的第二个节点;
- 指针推进,准备交换下一组节点; ........
- 返回 dummy.next,即交换后的头结点;
如图:
代码实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var swapPairs = function (head) {
// 创建个dummy节点
let dummy = new ListNode();
// dummy的next指向head节点
dummy.next = head;
// 初始化上一个为dummy
let pre = dummy;
// 遍历head节点
while (head && head.next) {
// 缓存head的下一个节点
const next = head.next;
// 第一步,将当前节点指向下下个节点
head.next = next.next;
// 第二步,下个节点的next指向当前节点
next.next = head;
// 第三步,上一个节点next指向下一个节点
pre.next = next;
// 同步上个节点指向每组的第二个节点
pre = head;
// 下一次循环
head = head.next;
}
// 返回dummy的next节点,即新节点的头结点
return dummy.next;
};
反转链表 3 - 局部反转 (中等难度)
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5] 示例 2:
输入:head = [5], left = 1, right = 1 输出:[5]
来源: Leecode - 局部反转链表
分析
从 left 位置开始到 right 位置局部反转,需要定位反转区域的上一个节点leftHead
,第一个节点为pre
, 第二个节点为cur
, 第三个节点为next
,根据反转链表的思路执行反转进行反转即可,具体步骤:
- 初始化
pre、cur、leftHead
,定义dummy
空节点,dummy.next 指向head
,初始化p
节点指向 dummy; - 从第一个节点开始遍历到 left 的上一个节点,定义为
leftHead
,设置start
指向 leftHead,即开始反转的第一个节点,pre 指向 start,cur 指向 pre.next; - 遍历从 left 开始到 right 结束,反转这个区域的节点;
- 将 leftHead.next 指向 pre,即反转后的最后一个节点;
- 将 start.next 指向 cur,cur 为 right 的下一个节点;
- 返回 dummy.next;
如图:
代码实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} left
* @param {number} right
* @return {ListNode}
*/
var reverseBetween = function (head, left, right) {
// 如果没有节点,或者只有一个节点,原样返回
if (!head || !head.next) {
return head;
}
// 初始化当前节点、上一个节点、反转链表左侧第一个节点
let cur, pre, leftHead;
// 定义dummy节点
let dummy = new ListNode();
// dummy的next指向头结点
dummy.next = head;
// 缓存dummy节点,复制给p
let p = dummy;
// 链表p走到左侧开始反转节点
for (let i = 0; i < left - 1; i++) {
p = p.next;
}
// 定义反转链表前结点
leftHead = p;
// 定义第一个节点
let start = leftHead.next;
// 定义上一个节点
pre = start;
// 初始化当前节点
cur = pre.next;
// 从left到right反转设置
for (let i = left; i < right; i++) {
let next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
// 将反转链表的上一个指向反转后的第一个
leftHead.next = pre;
// 反转后的最后一个节点指向后面未反转的链表
start.next = cur;
return dummy.next;
};