文章目录
- 重排链表
- 我的思路
- 网上思路
- 删除链表的倒数第 N 个结点
- 我的思路
- 网上思路
- 总结
重排链表
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
图一
图二
示例1:(图一)
输入:head = [1,2,3,4]
输出:[1,4,2,3]
示例2:(图二)
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]
我的思路
- 找到中间节点
- 反转后半部分链表
- 合并两个链表
网上思路
递归
我的思路
var reorderList = function(head) {
if (!head || !head.next) return;
// Step 1: 找到中间节点
let slow = head, fast = head;
while (fast.next && fast.next.next) {
slow = slow.next;
fast = fast.next.next;
}
// Step 2: 反转后半部分链表
let secondHalf = slow.next;
slow.next = null;
let prev = null;
while (secondHalf) {
let next = secondHalf.next;
secondHalf.next = prev;
prev = secondHalf;
secondHalf = next;
}
// Step 3: 合并两个链表
let first = head, second = prev;
while (second) {
let t1 = first.next, t2 = second.next;
first.next = second;
second.next = t1;
first = t1;
second = t2;
}
};
讲解
- 找到中间节点:首先,我们需要找到链表的中间节点。这可以通过快慢指针技术来实现。快指针每次移动两步,慢指针每次移动一步。当快指针到达链表尾部时,慢指针将指向链表的中间节点。
- 反转后半部分链表:找到中间节点后,我们需要反转链表的后半部分。这可以通过迭代的方式实现,每次将当前节点的下一个节点指向前一个节点。
- 合并两个链表:最后,我们将链表的前半部分和反转后的后半部分交错合并。我们交替地从两个链表中取出节点,将它们连接在一起。
网上思路
const reorderList = (head) => {
// 1. 设置链表栈
const headList = [];
// 2. 设置链表长度
let length = 0;
// 3. 设置链表位置
let pos = 0;
// 4. 设置链表一半
let harf = 0;
// 5. 递归方法体
const recursion = (head) => {
// 5.1 链表位置 + 1
pos++;
// 5.2 链表长度 + 1
length++;
// 5.3 如果到链表尾
if (!head) {
// 5.4 获取链表一半长度,5 -> 3;4 -> 2
harf = Math.round(pos / 2);
// 5.5 终止链表递归
return;
}
// 5.6 进一步递归
recursion(head.next);
// 5.7 链表位置 - 1
pos--;
/*
5.8 链表长度为奇数的情况
假设链表为 1->2->3->4->null,它的长度为 5(算上 null),harf 为 3
那么,pos 大于 harf,即 pos 为 4 的时候,收入链表栈 headList 中
pos 为 3 的时候,head.next 为 null
pos 小于 3 - 1 的时候,我们推出链表栈 headList 中的元素
*/
/*
5.9 链表长度为偶数的情况
假设链表为 1->2->3->4->5->null,它的长度为 6,harf 为 3
那么,pos 大于 harf,即 pos 为 5 和 4 的时候,收入链表栈 headList 中
pos 为 3 的时候,head.next 为 null
pos 小于 3 的时候,我们推出链表栈 headList 中的元素
*/
if (pos > harf) {
headList.push(head);
} else if (pos === harf) {
head.next = null;
} else if (length % 2 !== 0 && pos < harf - 1) {
const slice = headList.pop();
slice.next = head.next;
head.next = slice;
} else if (length % 2 === 0 && pos < harf) {
const slice = headList.pop();
slice.next = head.next;
head.next = slice;
}
};
// 6. 递归当前链表
recursion(head);
// 7. 返回链表(测试用,提交的时候不需要)
return head;
};
讲解
详情见代码和注释
删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
图三
示例 1:(图三)
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
我的思路
双指针
网上思路
递归+迭代
我的思路
var removeNthFromEnd = function (head, n) {
// 创建一个哑节点作为辅助,防止删除头节点时的边界问题
let dummy = new ListNode(0);
dummy.next = head;
let slow = dummy;
let fast = dummy;
// 让fast先走n+1步,因为dummy的存在,所以fast走到n步时,slow恰好在正确的位置
for (let i = 0; i <= n; i++) {
fast = fast.next;
}
// 现在两个指针一起走,当fast走到链表尾部时,slow刚好在待删节点的前一个位置
while (fast !== null) {
slow = slow.next;
fast = fast.next;
}
// 删除slow.next指向的节点
slow.next = slow.next.next;
// 返回真正的头节点
return dummy.next;
};
讲解
解决这个问题,一个常见的策略是使用“快慢指针”技巧。这个方法的关键是先让一个指针(快指针)提前走 n步,然后两个指针(快指针和慢指针)一起前进,当快指针到达链表尾部时,慢指针所在的位置就是要删除的节点的前一个位置。接下来,只需要做适当的指针调整就可以删除目标节点。
- 初始化两个指针:slow和fast ,均指向链表头部。
- 快指针先走:让 fast指针 先向前移动 n步。
- 同步前进:如果 fast 还没有到达链表尾部,那么 slow和fast 同时向前移动。
- 定位删除节点:当 fast 到达链表尾部时,slow所指向的节点就是待删除节点的前一个节点。
- 删除节点:调整指针,跳过待删除的节点。
- 特殊情况处理:如果要删除的是头节点,需要特殊处理。
网上思路
// 方法一:递归
const removeNthFromEnd = (head, n) => {
// 1. 设置倒数节点
let lastN = 0;
// 2. 设置递归
const recursion = (head) => {
// 2.1 如果抵达链表尾,返回 null
if (!head) {
return null;
}
// 2.2 设置 next 表示下一个节点
const next = recursion(head.next);
// 2.3 每次递归的【归】环节,lastN + 1
lastN++;
// 2.4 如果 lastN 和 n 相同,则进行删节点操作
if (lastN === n) {
head = next;
}
// 2.5 再【归】一层后,修正 next 指向
if (lastN === n + 1) {
head.next = next;
}
// 2.6 返回最终节点
return head;
};
// 3. 调用递归
return recursion(head);
};
// 方法二:迭代
const removeNthFromEnd = (head, n) => {
/* —————— 第一部分 —————— */
// 1. 设置 nowHead 为数组型链表:[1, 2, 3]...
const nowHead = [head];
// 2. 设置 tempHead 来扫描一趟,获取 nowHead
let tempHead = head;
// 3. 每次让 tempHead 前进,添加到 nowHead 中
while (tempHead.next) {
nowHead.push(tempHead.next);
tempHead = tempHead.next;
}
/* —————— 第二部分 —————— */
// 1. 设置倒数数字
let lastN = 0;
// 2. 判断是否需要删除头部
let isHead = true;
// 3. 如果 nowHead 存在内容
while (nowHead.length) {
// 3.1 倒数数字 + 1
lastN++;
// 3.2 获取当前部分的链表
const now = nowHead.pop();
// 3.3 如果不是头部,例如删除 [1, 2, 3] 中的 2 或者 3
if (lastN - 1 === n) {
// 3.3.1 表明我们删掉了非头部的节点
isHead = false;
// 3.3.2 让它指向 next.next
now.next = now.next.next;
}
}
// 4. 如果我们没有做删除操作,说明需要删除头部
if (isHead) {
head = head.next;
}
// 5. 返回最终链表
return head;
};
讲解
具体解法见注释
总结
今天较忙,网上思路暂未自己讲解,得自己先看注释,后续有时间再写