【经典算法】LeetCode24. 两两交换链表中的节点(Java/C/Python3/Golang实现含注释说明,中等)

标签(题目类型):链表

题目描述

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

在这里插入图片描述


示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:

输入:head = []
输出:[]
示例 3:

输入:head = [1]
输出:[1]
 

提示:

链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100

原题:Leetcode 24. 两两交换链表中的节点

思路及实现

方式一:迭代法(使用哑节点)

思路

迭代法通过遍历链表,在遍历过程中将相邻的节点进行交换。为了处理链表头部节点可能也参与交换的情况,通常会在链表头部添加一个哑节点(dummy node),这样就不需要单独处理头节点的情况。

Java版本
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode swapPairs(ListNode head) {
        // 创建一个虚拟头节点,方便处理头节点交换的情况
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        // prev 用于指向当前要交换节点的前一个节点
        ListNode prev = dummy;
        
        while (head != null && head.next != null) {
            // next 用于指向当前要交换的下一个节点
            ListNode next = head.next;
            
            // 交换当前节点和下一个节点的位置
            prev.next = next;
            head.next = next.next;
            next.next = head;
            
            // 更新指针位置
            prev = head;
            head = head.next;
        }
        
        // 返回交换后的链表
        return dummy.next;
    }
}

说明:

  • 首先创建了一个虚拟头节点,其next指向原链表的头节点。这样做的好处是,当需要交换头节点时,我们可以像交换其他节点一样处理,无需特殊处理头节点的情况。
  • prev指针用于指向当前要交换节点的前一个节点,初始化为虚拟头节点。
  • head指针指向当前要交换的节点,初始化为原链表的头节点。
  • next指针指向当前要交换的下一个节点。
  • 在每次迭代中,我们先交换head和next的位置,然后更新prev、head和next的指向,直到遍历完整个链表。
C语言版本
#include <stdlib.h>

typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;

ListNode* swapPairs(ListNode* head) {
    // 创建一个虚拟头节点
    ListNode* dummy = (ListNode*)malloc(sizeof(ListNode));
    dummy->next = head;
    
    // prev 用于指向当前要交换节点的前一个节点
    ListNode* prev = dummy;
    
    while (head != NULL && head->next != NULL) {
        // next 用于指向当前要交换的下一个节点
        ListNode* next = head->next;
        
        // 交换当前节点和下一个节点的位置
        prev->next = next;
        head->next = next->next;
        next->next = head;
        
        // 更新指针位置
        prev = head;
        head = head->next;
    }
    
    // 返回交换后的链表
    return dummy->next;
}

说明:

C语言版本的实现与Java版本类似,只是由于C语言需要手动管理内存,所以在创建虚拟头节点时需要使用malloc分配内存。

Python3版本
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        # 创建一个虚拟头节点
        dummy = ListNode(0)
        dummy.next = head
        
        # prev 用于指向当前要交换节点的前一个节点
        prev = dummy
        
        while head and head.next:
            # next 用于指向当前要交换的下一个节点
            next_node = head.next
            
            # 交换当前节点和下一个节点的位置
            prev.next = next_node
            head.next = next_node.next
            next_node.next = head
            
            # 更新指针位置
            prev = head
            head =head.next
        
        # 返回交换后的链表
        return dummy.next

说明:

Python版本的实现与Java版本非常相似,只是语法上有些差异。Python不需要显式声明变量类型,且使用None表示空指针。

Golang版本
package main

import (
	"fmt"
)

type ListNode struct {
	Val  int
	Next *ListNode
}

func swapPairs(head *ListNode) *ListNode {
	// 创建一个虚拟头节点
	dummy := &ListNode{0, head}
	
	// prev 用于指向当前要交换节点的前一个节点
	prev := dummy
	
	for head != nil && head.Next != nil {
		// next 用于指向当前要交换的下一个节点
		nextNode := head.Next
		
		// 交换当前节点和下一个节点的位置
		prev.Next = nextNode
		head.Next = nextNode.Next
		nextNode.Next = head
		
		// 更新指针位置
		prev = head
		head = head.Next
	}
	
	// 返回交换后的链表
	return dummy.Next
}

func main() {
	// 示例链表 1->2->3->4
	head := &ListNode{1, &ListNode{2, &ListNode{3, &ListNode{4, nil}}}}
	
	// 交换链表中的节点
	newHead := swapPairs(head)
	
	// 打印交换后的链表
	for newHead != nil {
		fmt.Printf("%d ", newHead.Val)
		newHead = newHead.Next
	}
}

说明:

Golang版本的实现与其他语言版本类似,只是语法和包结构有所不同。在Golang中,我们通常将函数和方法定义在包中,并在main函数中调用这些函数来执行程序。

复杂度分析

  • 时间复杂度:O(n),其中n是链表的长度。我们需要遍历链表中的每个节点一次。
  • 空间复杂度:O(1)。我们只使用了几个指针变量来跟踪链表中的节点,没有使用额外的数据结构来存储节点。

方式二:迭代法(不使用哑节点)

思路

不使用哑节点,而是直接在原链表上进行操作。这种方法稍微复杂一些,因为需要处理头节点可能发生变化的情况。

代码实现

Java版本
public class Solution {
    public ListNode swapPairs(ListNode head) {
        // 如果链表为空或只有一个节点,则无需交换
        if (head == null || head.next == null) {
            return head;
        }
        
        // 记录头节点的前一个节点(此处为空,因为头节点没有前一个节点)
        ListNode prev = null;
        // 当前节点
        ListNode curr = head;
        
        while (curr != null && curr.next != null) {
            // 记录当前节点对的第二个节点
            ListNode nextNode = curr.next;
            
            // 如果不是头节点,则处理前一个节点与第二个节点的连接
            if (prev != null) {
                prev.next = nextNode;
            }
            
            // 交换节点对
            curr.next = nextNode.next;
            nextNode.next = curr;
            
            // 更新prev和curr到下一个需要处理的节点对
            prev = curr;
            curr = curr.next;
        }
        
        // 返回新的头节点(原头节点的下一个节点)
        return head.next;
    }
}
C语言版本

#include <stdio.h>  
#include <stdlib.h>  
  
typedef struct ListNode {  
    int val;  
    struct ListNode *next;  
} ListNode;  
  
ListNode* swapPairs(ListNode* head) {  
    ListNode *prev = NULL;  
    ListNode *curr = head;  
  
    while (curr != NULL && curr->next != NULL) {  
        ListNode *next = curr->next;  
  
        // 进行节点交换  
        curr->next = next->next;  
        if (prev != NULL) {  
            prev->next = next;  
        }  
        next->next = curr;  
  
        // 更新prev和curr  
        prev = curr;  
        curr = curr->next;  
    }  
  
    // 如果prev不为空,说明head已经指向了第二个节点  
    return prev != NULL ? head->next : head;  
}  
  
void printList(ListNode* head) {  
    ListNode *current = head;  
    while (current != NULL) {  
        printf("%d ", current->val);  
        current = current->next;  
    }  
    printf("\n");  
}  
  
int main() {  
    // 创建示例链表:1 -> 2 -> 3 -> 4  
    ListNode *one = (ListNode*)malloc(sizeof(ListNode));  
    ListNode *two = (ListNode*)malloc(sizeof(ListNode));  
    ListNode *three = (ListNode*)malloc(sizeof(ListNode));  
    ListNode *four = (ListNode*)malloc(sizeof(ListNode));  
    one->val = 1;  
    two->val = 2;  
    three->val = 3;  
    four->val =
Python版本
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        # 如果链表为空或只有一个节点,则无需交换
        if not head or not head.next:
            return head
        
        # 当前节点
        curr = head
        
        # 当链表至少有两个节点时,进行交换
        while curr and curr.next:
            # 记录当前节点对的第二个节点
            next_node = curr.next
            
            # 处理前一个节点与第二个节点的连接(如果是头节点,则不处理)
            if curr != head:
                prev.next = next_node
            
            # 交换节点对
            curr.next = next_node.next
            next_node.next = curr
            
            # 更新prev和curr到下一个需要处理的节点对
            prev = curr
            curr = curr.next
        
        # 返回新的头节点(原头节点的下一个节点)
        return head.next if head != curr else head
Go版本
package main  
  
import "fmt"  
  
type ListNode struct {  
    Val  int  
    Next *ListNode  
}  
  
func swapPairs(head *ListNode) *ListNode {  
    dummy := &ListNode{Next: head} // 创建一个哑节点简化操作  
    prev := dummy  
    curr := head  
  
    for curr != nil && curr.Next != nil {  
        next := curr.Next  
        curr.Next = next.Next  
        next.Next = curr  
        prev.Next = next  
        prev = curr  
        curr = curr.Next  
    }  
  
    return dummy.Next // 返回哑节点后的链表  
}  
  
func printList(head *ListNode) {  
    for head != nil {  
        fmt.Printf("%d ", head.Val)  
        head = head.Next  
    }  
    fmt.Println()  
}  
  
func main() {  
    // 创建示例链表:1 -> 2 -> 3 -> 4  
    one := &ListNode{Val: 1}  
    two := &ListNode{Val: 2}  
    three := &ListNode{Val: 3}  
    four := &ListNode{Val: 4}  
    one.Next = two  
    two.Next = three  
    three.Next = four  
  
    // 调用swapPairs函数交换节点对  
    newHead := swapPairs(one)  
  
    // 打印结果链表  
    printList(newHead)  
}

复杂度分析

  • 时间复杂度:O(n),其中n是链表的长度。每个节点最多被访问两次。
  • 空间复杂度:O(1)。除了几个用于交换的指针变量外,没有使用额外的数据结构。

这种方法与使用哑节点的方法相比,稍微复杂一些,因为需要特别处理头节点的情况。但是,如果不希望使用额外的哑节点,这种方法是一个不错的选择。

方式三:递归法

思路

递归法的主要思路是,先递归地交换链表的后半部分,然后处理当前节点和下一个节点的交换,最后返回交换后的链表。

代码实现

Java版本
public class Solution {
    public ListNode swapPairs(ListNode head) {
        // 递归终止条件:当链表为空或只有一个节点时,无需交换,直接返回
        if (head == null || head.next == null) {
            return head;
        }
        
        // 递归交换后半部分链表
        ListNode newHead = swapPairs(head.next.next);
        
        // 处理当前节点和下一个节点的交换
        ListNode next = head.next;
        next.next = head;
        head.next = newHead;
        
        // 返回交换后的链表
        return next;
    }
}

说明:

  • 递归终止条件是当链表为空或只有一个节点时,此时无需交换,直接返回原链表。
  • 递归调用swapPairs(head.next.next)来交换后半部分链表,并将返回的新头节点保存在newHead中。
  • 处理当前节点和下一个节点的交换,并更新指针指向。
C语言版本
struct ListNode* swapPairs(struct ListNode* head) {
    // 递归终止条件
    if (head == NULL || head->next == NULL) {
        return head;
    }
    
    // 递归交换后半部分链表
    struct ListNode* newHead = swapPairs(head->next->next);
    
    // 处理当前节点和下一个节点的交换
    struct ListNode* next = head->next;
    next->next = head;
    head->next = newHead;
    
    // 返回交换后的链表
    return next;
}

说明:

C语言版本的递归实现与Java版本相似,只是语法上有所差异。

Python3版本
class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        # 递归终止条件
        if not head or not head.next:
            return head
        
        # 递归交换后半部分链表
        new_head = self.swapPairs(head.next.next)
        
        # 处理当前节点和下一个节点的交换
        next_node = head.next
        next_node.next = head
        head.next = new_head
        
        # 返回交换后的链表
        return next_node

说明:

Python版本的递归实现与Java版本非常相似,只是语法上有所差异。

复杂度分析

  • 时间复杂度:O(n),其中n是链表的长度。递归函数会遍历链表中的每个节点一次。
  • 空间复杂度:O(n),其中n是链表的长度。递归函数需要用到栈空间,最坏情况下,递归深度为n,因此空间复杂度为O(n)。

需要注意的是,递归方法的空间复杂度并不是最优的,因为它使用了额外的栈空间。在实际应用中,如果链表较长,可能会导致栈溢出。因此,在实际编程中,通常更倾向于使用迭代方法。

总结

方式优点缺点时间复杂度空间复杂度其他
迭代法(使用哑节点)代码简洁,易于理解需要额外的哑节点O(n)O(1)适用于链表头部处理
迭代法(不使用哑节点)不需要额外空间(除了变量)处理链表头部时稍复杂O(n)O(1)需特别注意链表头部的变化
递归法代码优雅,易于理解可能存在栈溢出风险,空间效率不如迭代法O(n)O(n)(递归栈空间)适用于链表长度较短的情况

在以上三种方式中,迭代法(不使用哑节点)和递归法都是直接对原链表进行操作,不需要额外的节点空间(除了必要的变量)。然而,递归法由于使用系统栈来保存递归调用的状态,空间复杂度为O(n),在链表较长时可能会导致栈溢出。迭代法则没有这个问题,空间复杂度为O(1)。使用哑节点的方式可以简化链表头部的处理,使得代码更简洁,但会占用额外的空间(尽管这个空间是固定的,不随链表长度变化)。

相似题目

以下是与链表操作相关的一些相似题目及其题目名:

相似题目难度链接
移除链表元素中等力扣-203
合并两个有序链表中等力扣-21
链表中倒数第k个节点中等力扣-19
链表的中间节点简单力扣-876
环形链表中等力扣-141
链表的回文结构简单力扣-234
分割链表中等力扣-143
复制带随机指针的链表中等力扣-138
两个链表的第一个公共节点中等力扣-142
有序链表转换二叉搜索树中等力扣-109

这些题目涵盖了链表操作中的多个方面,包括节点的插入、删除、反转、查找、合并等,有助于深入理解链表数据结构及其相关算法。通过解决这些题目,可以进一步提升链表操作的熟练度和解决问题的能力。

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来用文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统,动态规划也广泛应用于各种优化算法,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法LeetCode的动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列,第j个元素的值。 在LeetCode,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值