一、题目是啥?一句话说清
给定一个单链表,将所有奇数索引节点和偶数索引节点分别分组,保持原有顺序,然后将偶数索引节点组连接到奇数索引节点组之后。
示例:
- 输入:1 → 2 → 3 → 4 → 5
- 输出:1 → 3 → 5 → 2 → 4
二、解题核心
使用两个指针分别构建奇数链表和偶数链表,遍历原链表,将奇数节点连接在一起,偶数节点连接在一起,最后将偶数链表头连接到奇数链表尾。
这就像把队伍分成男女两组,然后让男组在前,女组在后,但每组内部保持原来的顺序。
三、关键在哪里?(3个核心点)
想理解并解决这道题,必须抓住以下三个关键点:
1. 分离奇偶节点
- 是什么:使用两个指针odd和even,odd指向奇数节点,even指向偶数节点。
- 为什么重要:这样我们可以同时遍历和构建两个链表,而不需要额外空间。
2. 维护两个链表的头部
- 是什么:保存偶数链表的头节点evenHead,因为最后需要将偶数链表连接到奇数链表之后。
- 为什么重要:如果不保存evenHead,我们将丢失偶数链表的起点,无法连接。
3. 指针移动和连接
- 是什么:在遍历过程中,将odd的next指向odd的下一个奇数节点(即odd->next->next),同样对even做类似操作。
- 为什么重要:这确保了奇数节点和偶数节点分别被正确连接,形成两个独立的链表。
四、看图理解流程(通俗理解版本)
假设链表为:1 → 2 → 3 → 4 → 5
-
初始化:
- odd指向头节点1(奇数索引)
- even指向头节点的下一个节点2(偶数索引)
- 保存evenHead = 2(偶数链表的头)
-
第一轮处理:
- 将odd的next指向odd->next->next(即1指向3):1 → 3
- 将even的next指向even->next->next(即2指向4):2 → 4
- 移动odd到odd->next(即3)
- 移动even到even->next(即4)
-
第二轮处理:
- odd的next指向odd->next->next(即3指向5):1 → 3 → 5
- even的next指向even->next->next(即4指向null):2 → 4 → null
- 移动odd到5
- 移动even到null
-
连接链表:
- 此时奇数链表:1 → 3 → 5
- 偶数链表:2 → 4
- 将odd的next指向evenHead:5指向2,形成1 → 3 → 5 → 2 → 4
五、C++ 代码实现(附详细注释)
cpp
#include <iostream>
using namespace std;
// 链表节点定义
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return head; // 空链表或只有一个节点,直接返回
}
ListNode* odd = head; // 奇数指针,指向头节点
ListNode* even = head->next; // 偶数指针,指向第二个节点
ListNode* evenHead = even; // 保存偶数链表的头节点
// 遍历链表,直到even或even->next为空
while (even != nullptr && even->next != nullptr) {
// 将odd的next指向下一个奇数节点
odd->next = odd->next->next;
// 将even的next指向下一个偶数节点
even->next = even->next->next;
// 移动odd和even指针
odd = odd->next;
even = even->next;
}
// 将偶数链表连接到奇数链表之后
odd->next = evenHead;
return head;
}
};
// 辅助函数:打印链表
void printList(ListNode* head) {
while (head != nullptr) {
cout << head->val << " ";
head = head->next;
}
cout << endl;
}
// 测试代码
int main() {
// 创建示例链表:1->2->3->4->5
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
head->next->next->next = new ListNode(4);
head->next->next->next->next = new ListNode(5);
Solution solution;
ListNode* result = solution.oddEvenList(head);
printList(result); // 输出:1 3 5 2 4
// 释放内存(简单示例)
while (result != nullptr) {
ListNode* temp = result;
result = result->next;
delete temp;
}
return 0;
}
六、时间空间复杂度
- 时间复杂度:O(n),其中n是链表长度。我们只遍历了一次链表。
- 空间复杂度:O(1),只使用了几个指针,没有使用额外空间。
七、注意事项
- 边界条件:处理空链表或只有一个节点的情况,直接返回头节点。
- 循环条件:循环继续的条件是even不为空且even->next不为空,因为even指针每次移动两步,需要确保even->next存在。
- 指针移动:在更新odd和even的next指针后,需要移动odd和even指针到下一个位置。
- 连接链表:最后一定要将奇数链表的尾部指向偶数链表的头部,否则会丢失偶数部分。
算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
【算法--链表】117.填充每个节点的下一个右侧节点指针Ⅱ--通俗讲解
【算法--链表】138.随机链表的复制--通俗讲解
【算法】143.重排链表--通俗讲解
【算法--链表】146.LRU缓存--通俗讲解
【算法--链表】147.对链表进行插入排序--通俗讲解
【算法】【链表】148.排序链表--通俗讲解
【算法】【链表】160.相交链表--通俗讲解
【算法】【链表】203.移除链表元素--通俗讲解
【算法】【链表】206.反转链表--通俗讲解
【算法】234.回文链表--通俗讲解
【算法】【链表】237.删除链表中的节点--通俗讲解
关注公众号,获取更多底层机制/ 算法通俗讲解干货!