题目链接:24. 两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
链表中节点的数目在范围?[0, 100]?内
0 <= Node.val <= 100
文章讲解:代码随想录
视频讲解:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点_哔哩哔哩_bilibili
思路:类似昨天做的反转链表(206. 反转链表),区别再于反转链表在遍历链表时只需要1步操作,将当前节点指向上1个节点,然后继续遍历链表剩余部分,重复这个过程。
如上图的红框部分,反转链表在每次循环中只需要对链表进行一次操作,当遍历完成时,整个链表也完成了反转。
本题目标是对链表中的元素进行两两反转,在遍历链表时就要多2步操作,除了需要交换当前遍历的节点对,还要将节点对的上个节点指向交换后节点对的前一个元素(即原节点对中后一个元素),将交换后节点对的后一个元素(即原节点对中前一个元素)指向节点对的下一个元素。
/**
* 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) {
if (head === null || head.next === null) {
return head;
}
const vHead = new ListNode(0, head); // 虚拟头节点
let pre = vHead, cur = head; // pre 指向当前要交换的节点对的前一个节点,cur 指向当前要交换的节点对
// 当要交换的节点对中两个节点都存在时,执行交换操作。
while (cur && cur.next) {
// 假设链表节点顺序为0->1->2->3,pre 指向0,cur指向1,当前要交换的节点对为1和2,cur.next 指向2,cur.next.next 指向3
const temp = cur.next.next; // 先保存节点对的后一个节点的位置,即3的位置
cur.next.next = cur; // 将2指向1,此时链表顺序为0->1<->2 3
pre.next = cur.next; // 将0指向2,此时链表顺序为0->2<->1 3
cur.next = temp; // 将1指向3,此时链表顺序为0->2->1->3
// 更新 pre 和 cur,继续交换下一个节点对
pre = cur; // 将 pre 指向1
cur = temp;// 将 cur 指向3
}
return vHead.next; // 返回真实头节点
};
分析:链表的每次遍历中交换2个元素,遍历了 n / 2 次,时间复杂度为 O(n),空间复杂度为 O(1)。
思路:使用双指针法的思路,从左到右一边遍历一边递归的交换节点对。
递归解析:
/**
* 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) {
if (head === null || head.next === null) {
return head;
}
const vHead = new ListNode(0, head); // 虚拟头节点
const swap = function(pre, cur) {
// pre 指向当前要交换的节点对的前一个节点,cur 指向当前要交换的节点对
// 结束条件为当前要交换的节点对不存在或只有1个元素
if (cur === null || cur.next === null) {
return vHead.next; // 返回真实头节点
}
// 假设链表节点顺序为0->1->2->3,pre 指向0,cur指向1,当前要交换的节点对为1和2,cur.next 指向2,cur.next.next 指向3
const temp = cur.next.next; // 先保存节点对的后一个节点的位置,即3的位置
cur.next.next = cur; // 将2指向1,此时链表顺序为0->1<->2 3
pre.next = cur.next; // 将0指向2,此时链表顺序为0->2<->1 3
cur.next = temp; // 将1指向3,此时链表顺序为0->2->1->3
return swap(cur, temp); // 递归的交换下一个节点对
};
return swap(vHead, head);
};
分析:递归的遍历了一次整个链表,同时调用了 n / 2 层栈空间,时间复杂度为 O(n),空间复杂度为 O(n)。
思路:当链表为空链表或只有1个元素时,,对链表的节点进行两两交换后的结果就是它本身。链表元素大于等于2个时,递归的对第3个元素开始的链表部分进行两两交换,再交换前2个元素,即可得到对整个链表进行两两交换的结果。
递归解析:
/**
* 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) {
// 当前链表为空链表或只有1个元素时,对链表的节点进行两两交换后的结果就是它本身
if (head === null || head.next === null) {
return head;
}
// 两两交换从第3个节点开始的链表部分的元素
const start = swapPairs(head.next.next);
// 交换链表第1个节点和第2个节点
// 假设目前链表结构为1->2->3->...,此时 start 指向3,head 指向1,head.next 指向2
head.next.next = head; // 将2指向1,此时链表结构为1<->2 3->...
head = head.next; // 将head指向2,此时 head.next 指向1
head.next.next = start; // 将1指向3,此时链表结构为2->1->3->...,完成了前2个节点的交换
return head; // 返回链表头节点位置
};
分析:递归的遍历了一次整个链表,同时调用了 n / 2 层栈空间,时间复杂度为 O(n),空间复杂度为 O(n)。
再次使用了双指针法和递归法,对它们有了更清楚的认识,同时也体会到了探索一题多解的快乐。