【LeetCode】24. 两两交换链表中的节点(中等)——代码随想录算法训练营Day04

题目链接:24. 两两交换链表中的节点

题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

【LeetCode】24. 两两交换链表中的节点(中等)——代码随想录算法训练营Day04_第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

题解1:双指针

思路:类似昨天做的反转链表(206. 反转链表),区别再于反转链表在遍历链表时只需要1步操作,将当前节点指向上1个节点,然后继续遍历链表剩余部分,重复这个过程。

【LeetCode】24. 两两交换链表中的节点(中等)——代码随想录算法训练营Day04_第2张图片

如上图的红框部分,反转链表在每次循环中只需要对链表进行一次操作,当遍历完成时,整个链表也完成了反转。

本题目标是对链表中的元素进行两两反转,在遍历链表时就要多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)。

题解2:双指针递归

思路:使用双指针法的思路,从左到右一边遍历一边递归的交换节点对。

递归解析:

  • 函数功能:传入上一个节点和当前节点对,对整个链表中的元素进行两两交换。
  • 结束条件:当前节点对不存在或只有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)。

题解3:暴力递归

思路:当链表为空链表或只有1个元素时,,对链表的节点进行两两交换后的结果就是它本身。链表元素大于等于2个时,递归的对第3个元素开始的链表部分进行两两交换,再交换前2个元素,即可得到对整个链表进行两两交换的结果。

递归解析:

  • 函数功能:传入链表头节点,对整个链表中的元素进行两两交换。
  • 结束条件:链表为空链表或只有1个元素,返回这个链表的头节点。
  • 递归关系:对一个链表的元素进行两两交换,先对链表第3个节点即之后的部分的元素进行两两交换,再第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) {
    // 当前链表为空链表或只有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)。

收获

再次使用了双指针法和递归法,对它们有了更清楚的认识,同时也体会到了探索一题多解的快乐。

你可能感兴趣的:(代码随想录算法训练营,#,LeetCode,链表,算法,代码随想录算法训练营,链表)