挑战刷leetcode第11天( 链表-两两交换链表中的节点)

87 阅读5分钟

当链表遇上“交换舞伴”:Java与C++的奇妙双人舞

大家好,今天我们来聊聊链表中的“交换舞伴”问题。你可能会问,链表和舞伴有什么关系?别急,听我慢慢道来。

想象一下,你正在参加一场盛大的舞会,舞池里站着一排排的舞者(链表节点),他们手拉着手(指针),形成了一个有序的队伍。突然,DJ宣布:“现在,我们要让每对舞伴交换位置!”于是,舞池里开始了一场热闹的“交换舞伴”游戏。

问题描述

给定一个链表,你需要将每两个相邻的节点交换位置,并返回交换后的链表头节点。听起来很简单,对吧?但当你真正开始写代码时,可能会发现这并不像看起来那么容易。

Java实现

首先,我们来看看Java版本的“交换舞伴”代码:

public ListNode swapPairs(ListNode head) {
    ListNode node = new ListNode(0); // 创建一个虚拟头节点
    node.next = head;

    ListNode tmp = node;
    while (tmp.next != null && tmp.next.next != null) {
        ListNode node1 = tmp.next;
        ListNode node2 = tmp.next.next;
        tmp.next = node2;
        node1.next = node2.next;
        node2.next = node1;
        tmp = node1;
    }
    return node.next;
}

C++实现

接下来,我们再来看看C++版本的“交换舞伴”代码:

ListNode* swapPairs(ListNode* head) {
    ListNode* node = new ListNode(0); // 创建一个虚拟头节点
    node->next = head;

    ListNode* tmp = node;

    while (tmp->next != nullptr && tmp->next->next != nullptr) {
        ListNode* node1 = tmp->next;
        ListNode* node2 = tmp->next->next;

        tmp->next = node2;
        node1->next = node2->next;
        node2->next = node1;
        tmp = node1;
    }
    return node->next;
}

代码解析

  1. 虚拟头节点:我们创建了一个虚拟头节点node,它的next指向真正的头节点head。这样做的好处是,我们不需要特别处理头节点的交换,简化了代码逻辑。

  2. 交换过程

    • tmp指向当前节点的前一个节点。
    • node1node2分别指向需要交换的两个节点。
    • 通过调整指针,将node1node2的位置交换。
    • 最后,tmp移动到node1的位置,继续下一轮交换。
  3. 循环条件:只有当tmp.nexttmp.next.next都不为空时,才进行交换。这确保了我们在交换时不会出现空指针异常。

递归版Java代码

public ListNode swapPairs(ListNode head) {
    // 递归终止条件:如果链表为空,或者只有一个节点,直接返回
    if (head == null || head.next == null) {
        return head;
    }

    // 保存第二个节点
    ListNode two = head.next;
    // 保存第三个节点(即下一对的起点)
    ListNode three = two.next;

    // 交换当前两个节点
    two.next = head; // 第二个节点指向第一个节点
    head.next = swapPairs(three); // 第一个节点指向下一对交换后的头节点

    // 返回新的头节点(即原来的第二个节点)
    return two;
}

代码解析

  1. 递归终止条件

    • 如果链表为空(head == null),或者只有一个节点(head.next == null),直接返回当前节点。这是递归的“出口”,防止无限递归。
  2. 交换逻辑

    • 我们首先保存第二个节点(two)和第三个节点(three)。
    • 然后让第二个节点指向第一个节点(two.next = head),完成局部的交换。
    • 接着,让第一个节点指向下一对交换后的头节点(head.next = swapPairs(three))。这一步是关键,它通过递归调用,解决了剩余链表的交换问题。
  3. 返回新头节点

    • 交换后,原来的第二个节点(two)变成了新的头节点,所以返回它。

递归的魔力

递归的魅力在于它的简洁和优雅。它把问题分解成两个部分:

  1. 当前问题:交换当前的两个节点。
  2. 子问题:递归处理剩下的链表。

通过这种方式,递归把复杂的链表操作简化成了一个简单的模式:交换当前两个节点,然后递归处理剩下的部分


举个例子

假设链表是 1 -> 2 -> 3 -> 4,我们来模拟一下递归的过程:

  1. 第一层递归

    • 当前节点是 1,第二个节点是 2,第三个节点是 3
    • 交换 1 和 2,得到 2 -> 1
    • 递归处理 3 -> 4
  2. 第二层递归

    • 当前节点是 3,第二个节点是 4,第三个节点是 null
    • 交换 3 和 4,得到 4 -> 3
    • 递归处理 null,直接返回。
  3. 合并结果

    • 第一层递归的结果是 2 -> 1,它指向第二层递归的结果 4 -> 3
    • 最终链表变为 2 -> 1 -> 4 -> 3

为什么坚持

你可能会问,为什么要坚持写这样的代码?其实,这不仅仅是为了完成任务,更是为了锻炼我们的逻辑思维和编程能力。每一次的代码优化和调试,都是对我们耐心和毅力的考验。正如在舞会上,每一次的舞伴交换都需要精准的配合和默契,编程也是如此。

幽默时刻

想象一下,如果链表中的节点也有情感,那么每次交换位置时,它们可能会说:

  • node1:嘿,node2,我们换个位置吧!
  • node2:好啊,不过你得先松开我的手,我才能让你去拉node3的手。
  • node1:没问题,我已经准备好了!
  • tmp:别忘了我,我可是你们的“媒人”,没有我,你们可没法顺利交换!

总结

通过Java和C++两种语言的实现,我们不仅学会了如何交换链表中的相邻节点,还体会到了编程中的乐趣和挑战。每一次的代码编写,都是我们与计算机的一次“对话”,而每一次的调试和优化,都是我们与逻辑的一次“较量”。

所以,下次当你遇到类似的编程问题时,不妨想象一下,你正在指挥一场盛大的舞会,而你的任务就是让每一位舞者都能找到自己的最佳位置。坚持下去,你会发现,编程不仅仅是一门技术,更是一种艺术。

好了,今天的“交换舞伴”就到这里,希望大家在编程的舞池中,跳出属于自己的精彩舞步!