61. 旋转链表
「题目:」
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
「示例:」
输入:head = [1,2,3,4,5], k = 2, 输出:[4,5,1,2,3].
「解题思路:」
记链表的长度为 n,如果 k 大于等于 n 时,只需要向右移动 k % n 次即可,因为每 n 次移动,链表会恢复原状。
由上图可知(链表题型画完图,能很清晰地梳理出节点之间的引用变换):
遍历链表,获取到链表的长度,那么分割点即为 length - (k % length),这里需要将前置节点的 next 设置为 null,所以分割点的位置需要向前移动一位
遍历到链表的尾节点,将其指向旧链表的头节点。
一定要舍得使用临时变量来管理链表节点的引用。
时间复杂度:O(n),空间复杂度:O(1)。
const rotateRight = (head, k) => {
if (!head || !head.next) {
return head;
}
let length = 0;
let currentHead = head;
while (currentHead) {
length++;
currentHead = currentHead.next;
}
let index = length - (k % length) - 1;
currentHead = head;
while (index) {
currentHead = currentHead.next;
index--;
}
let lastHead = currentHead;
while (lastHead.next) {
lastHead = lastHead.next;
}
lastHead.next = head;
const ans = currentHead.next;
currentHead.next = null;
return ans;
}
92. 反转链表 II
「题目:」
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回反转后的链表。
「示例:」
输入:head = [1,2,3,4,5], left = 2, right = 4, 输出:[1,4,3,2,5].
「第一种解题思路:先将需要翻转的区间剥离成单独的链表,并且保存该链表的前驱节点和后继节点,然后对其进行翻转操作,最后将前驱节点和后继节点关联上。」
时间复杂度:O(n),空间复杂度:O(1)。
const reverseBetween = (head, m, n) => {
const dummyHead = new ListNode(null);
dummyHead.next = head;
let preHead = dummyHead;
for (let i = 0; i < m - 1; i++) {
preHead = preHead.next;
}
let rightHead = preHead;
for (let i = 0; i < n - m + 1; i++) {
rightHead = rightHead.next;
}
let leftHead = preHead.next;
let sufHead = rightHead.next;
preHead.next = null;
rightHead.next = null;
reverseLinkedList(leftHead);
preHead.next = rightHead;
leftHead.next = sufHead;
return dummyHead.next;
}
function reverseLinkedList(head) {
let pre = null;
let current = head;
while (current) {
const next = current.next;
current.next = pre;
pre = current;
current = next;
}
}
「第二种解题思路:上述方法的缺点主要在于两次遍历链表,接下来通过在反转区间中对每一个遍历的元素依次反转即可优化。」
「在遍历的过程,不断维护 preHead、current 和 next 这三个节点之间的指向关系。」
时间复杂度:O(n),空间复杂度:O(1)。
const reverseBetween = (head, m, n) => {
const dummyHead = new ListNode(null);
dummyHead.next = head;
let preHead = dummyHead;
for (let i = 0; i < m - 1; i++) {
preHead = preHead.next;
}
let current = preHead.next;
let next = null;
for (let i = 0; i < n - m; i++) {
next = current.next;
current.next = next.next;
next.next = preHead.next;
preHead.next = next;
}
return dummyHead.next;
}
1721. 交换链表中的节点
「题目:」
给你链表的头节点 head 和一个整数 k 。
交换 链表正数第 k 个节点和倒数第 k 个节点的值后,返回链表的头节点(链表 从 1 开始索引)。
「示例:」
输入:head = [1,2,3,4,5], k = 2;输出:[1,4,3,2,5]。
「第一种解题思路:首先通过一次遍历可以获取到第 k 个节点,并且计算出链表的总长度,这样就可以定位到倒数第 k 个节点,然后再通过一次遍历拿到倒数第 k 个节点,交换两者的值即可。」
时间复杂度:O(n+k),空间复杂度:O(1)。
const swapNodes = function(head, k) {
let length = 0;
let x = null;
let currentHead = head;
while (currentHead) {
length++;
if (length === k) {
x = currentHead;
}
currentHead = currentHead.next;
}
let k2 = length - k;
let y = null;
currentHead = head;
while (k2--) {
currentHead = currentHead.next;
}
y = currentHead;
const xval = x.val;
x.val = y.val;
y.val = xval;
return head;
};
「第二种解法:利用双指针技巧可以将上述解法的时间复杂度再次优化,实现真正意义上的一次遍历。」
时间复杂度:O(n),空间复杂度:O(1)。
const swapNodes = function(head, k) {
let currentHead1 = head;
let currentHead2 = head;
while (--k) {
currentHead1 = currentHead1.next;
}
const x = currentHead1;
while (currentHead1.next) {
currentHead1 = currentHead1.next;
currentHead2 = currentHead2.next;
}
const y = currentHead2
const temp = x.val;
x.val = y.val;
y.val = temp;
return head;
};
写在最后
「感谢您能耐心地读到这里,如果本文对您有帮助,欢迎点赞、分享、或者关注下方的公众号哟。」
相关链接:
https://leetcode-cn.com/problems/rotate-list/
https://leetcode-cn.com/problems/reverse-linked-list-ii/
https://leetcode-cn.com/problems/swapping-nodes-in-a-linked-list/