一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
1. 解题1
这里如果刷过K个一组翻转列表,那么这里两两交换链表中的节点就是K = 2的特殊情况。那么这里第一种解法可以直接使用那道题的成果
function swapPairs(head: ListNode | null, pre: ListNode | null = null): ListNode | null {
return reverseKGroup(head, 2)
}
2. 题解2
首先分析一下,题解1中存在的问题:
K个一组翻转列表的思路是:先计算总长度,然后计算每组需要翻转的节点的数量。这里计算节点的数量是为了表明翻转进度,如果当前组翻转结束,需要将接力棒交给下一组(重置cur和pre)。
但是在两两交换中,因为只涉及到两个节点,就不会有当前进度一说, 两两交换之后可以直接将cur和pre转给下一组,所以这里可以不需要统计长度。又考虑之类都是重复的操作,可以用递归实现
这里需要注意的是:
- 第一组翻转时,是没有
pre节点,同时需要将第二个节点作为结果返回;之后为了连接翻转之后的组,需要将前一组的尾结点传给下一组当pre节点 - 这里改变了头节点,但是为什么没有用哨兵节点?因为递归重新调用,不适合用哨兵节点
function swapPairs(head: ListNode | null, pre: ListNode | null = null): ListNode | null {
// 退出条件:没有或者只剩一个节点就不需要翻转
if (head === null || head.next === null) return head
// 要翻转的两个节点: head 和 head.next
let next = head.next
// 缓存下一组的开始节点
let nexnext = next.next
// 交换位置
head.next = head.next.next
next.next = head
// 第一组的pre为null,接下来所有组的pre为之前一组翻转之后的第二个节点
// 用来链接两个组
if (pre) {
pre.next = next
}
swapPairs(nexnext, head)
return next
};
3. 官方题解
var swapPairs = function(head) {
if (head === null|| head.next === null) {
return head;
}
const newHead = head.next;
// 先去翻转下一组,返回翻转之后的节点
// 然后 翻转链表
head.next = swapPairs(newHead.next);
newHead.next = head;
return newHead;
};
比较一下:题解2和官方题解:
2的解法是正向思维: 先翻转,然后转移控制权到下一组,然后再翻转,再转移。。。以此类推;
而3的解法是逆向思维,先找到下一组的头节点,然后转移控制器,再找下一组头节点。。。等到最后一组,直接返回或者翻转返回 翻转后的头节点,再处理倒数第二组。。。虽然正式思维更容易理解,但是递归相关问题都是用逆向思维实现的,比如最常见的斐波那契数列问题。所以,官方题解的思路是更正确的递归的思路。
4. 迭代
一般可以用递归实现的都能用迭代实现,迭代也是正向思维,跟解法2相似的逻辑
function swapPairs(head: ListNode | null): ListNode | null {
const dummy = new ListNode(-1, head)
let cur = head, pre = dummy
while(cur && cur.next) {
// 交换
let next = cur.next
let nexnext = next.next
cur.next = nexnext
next.next = cur
pre.next = next
// 下一组
pre = cur
cur = nexnext
}
return dummy.next
};