【刷题日记】两两交换链表中的节点

48 阅读2分钟

1、 题目描述

LeetCode 24.两两交换链表中的节点

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

graph LR
   A((node1=1)) --> B((node2=2))
   B --> C((3))
   C --> D((4))
	 style B fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
	 style D fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px

	 E((node2=2)) --> F((node1=1))
	 F --> G((4))
	 G --> H((3))
	 style E fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
	 style G fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px

  

2、思路

输入链表、顺序交换链表中的两两节点,返回交换后的链表。 与递归的逻辑相同(将大问题分解成相似的小问题) 总所周知~ 能用递归的算法最终都可以通过迭代实现,所以这道题基本可以得到递归与迭代两种解法。

递归:

  • 优点:代码逻辑简单、通过终止条件自动处理边界
  • 缺点:空间复杂度较大、有栈溢出风险

迭代:

  • 优点:易于调试、空间复杂度底
  • 缺点:代码逻辑稍复杂

3、代码

递归

 /**
  * Definition for singly-linked list.
  * public class ListNode {
  *     int val;
  *     ListNode next;
  *     ListNode() {}
  *     ListNode(int val) { this.val = val; }
  *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  * }
  */
 class Solution {
     public ListNode swapPairs(ListNode head) {
     // 递归退出条件
         if(head == null || head.next == null){
             return head;
        }
         // 新链表头节点
         ListNode newListHead = head.next;
         // 老链表头节点 指向交换后链表的头节点
         head.next = swapPairs(newListHead.next);
         // 新链表头节点 指向老链表头节点
         newListHead.next = head;
         // 返回新链表
         return newListHead;
    }
 }

迭代

 /**
  * Definition for singly-linked list.
  * public class ListNode {
  *     int val;
  *     ListNode next;
  *     ListNode() {}
  *     ListNode(int val) { this.val = val; }
  *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  * }
  */
 class Solution {
     public ListNode swapPairs(ListNode head) {
     // 构建空头节点(方便交换后返回新链表)
         ListNode tempHead = new ListNode(0);
         tempHead.next = head;
         // 临时指针(后续使用临时指针进行移动)
         ListNode tempPointer = tempHead;
         // 循环条件、符合条件才进行交换
         while(tempPointer.next != null && tempPointer.next.next != null){
         // 将 node1、node2 单独声明出来方便进行节点交换
             ListNode node1 = tempPointer.next;
             ListNode node2 = tempPointer.next.next;
             // 1. 头节点指向 node2
             tempPointer.next = node2;
             // 2. node1 指向 node2 后继节点
             node1.next = node2.next;
             // 3. node2 指向 node1
             node2.next = node1;
 // 以上1、2、3步后成功交换两个节点
 ​
 // node1 后续节点还没有交换,赋值给tempPointer后 循环进行后面节点的交换逻辑
             tempPointer = node1;
        }
         // 返回头节点的后继节点 即为交换后的链表
         return tempHead.next;
    }
 }
 ​

迭代图式: 第一步:

graph LR
	 head(head=0) --> B
   A((1)) --> B((2))
   B --> C((3))
   C --> D((4))
	 style B fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
	 style D fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px

第二步:

graph LR
	 head(head=0) -->  B((2))
   A((1)) --> C
   B --> C((3))
   C --> D((4))
	 style B fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
	 style D fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px

第三步:

graph LR
	 head(head=0) -->  B((2))
   A((1)) --> C((3))
   B --> A
   C --> D((4))
	 style B fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
	 style D fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px

4、算法分析

复杂度分析

时间复杂度: 两种解法都是遍历一遍链表 时间复杂度相同为 O(n) 空间复杂度: 递归会占用额外的 O(n)级别的栈空间、迭代几乎没有耗费额外空间为O(1) 实际执行耗时与内存占用率: 在这里插入图片描述

在这里插入图片描述

5、总结

递归与迭代算法各有优劣、不同场景使用不同算法思路解决相同问题, 碰到一时难以解决的问题可以先通过递归写出解法,然后根据递归思路慢慢调试出迭代的写法。