1 题目
给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别分组,保持它们原有的相对顺序,然后把偶数索引节点分组连接到奇数索引节点分组之后,返回重新排序的链表。
第一个 节点的索引被认为是 奇数 , 第二个 节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
示例 1:

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

输入: head = [2,1,3,5,6,4,7]
输出: [2,3,6,7,1,5,4]
提示:
n ==链表中的节点数0 <= n <= 104-106 <= Node.val <= 106
2 代码实现
c++
cpp
/**
* Definition for singly-linked list.
* 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 ;
while (even != nullptr && even -> next != nullptr){
odd -> next = even -> next ;
odd = odd -> next ;
even -> next = odd -> next ;
even = even -> next ;
}
odd -> next = evenHead ;
return head;
}
};
js
javascript
/**
* 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 oddEvenList = function(head) {
if (head == null || head.next == null){
return head ;
}
let odd = head ;
let even = head.next ;
let evenHead = even ;
while(even && even.next){
odd.next = even.next;
odd = odd.next ;
even.next = odd.next ;
even = even.next ;
}
odd.next = evenHead;
return head ;
};
思考
这怎么做的,递归吗?我个人感觉没什么思路,现在的算法处于完蛋的状态,不知道怎么做。cpp怎么实现一下?
题解
你目前对这道题没思路很正常,这道题不适合用递归 (递归会产生 O (n) 栈空间,违反 O (1) 额外空间要求),核心解法是双指针遍历 + 链表拆分重组,全程仅用常数级额外空间,时间复杂度 O (n),完全符合题目要求。
核心思路
- 索引定义 :题目中第一个节点索引为奇数,第二个为偶数,依此类推(节点位置从 1 开始计数);
- 双指针分工 :用
odd指针指向奇数索引节点 ,even指针指向偶数索引节点,各自遍历对应分组的节点; - 链表拆分 :遍历过程中,将奇数节点和偶数节点分别串联成两个独立的子链表(奇链表、偶链表),保持原相对顺序;
- 链表重组:遍历结束后,将偶链表直接连接到奇链表的尾部,完成最终重组。
完整 C++ 实现代码
cpp
#include <iostream>
// 定义单链表节点结构(LeetCode原题默认结构)
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) {
// 边界情况:空链表或只有1个节点,直接返回原链表
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 = even->next; // 奇指针指向「当前偶数节点的下一个节点」(下一个奇数节点)
odd = odd->next; // 奇指针后移
even->next = odd->next; // 偶指针指向「当前奇数节点的下一个节点」(下一个偶数节点)
even = even->next; // 偶指针后移
}
odd->next = evenHead; // 重组:偶链表连接到奇链表尾部
return head; // 奇链表头节点即为最终链表头
}
};
// 测试代码:创建链表、调用函数、打印结果
void printList(ListNode* head) {
while (head != nullptr) {
std::cout << head->val << " ";
head = head->next;
}
std::cout << std::endl;
}
int main() {
// 示例1:输入[1,2,3,4,5]
ListNode* head1 = new ListNode(1);
head1->next = new ListNode(2);
head1->next->next = new ListNode(3);
head1->next->next->next = new ListNode(4);
head1->next->next->next->next = new ListNode(5);
Solution s;
ListNode* res1 = s.oddEvenList(head1);
printList(res1); // 输出:1 3 5 2 4
// 示例2:输入[2,1,3,5,6,4,7]
ListNode* head2 = new ListNode(2);
head2->next = new ListNode(1);
head2->next->next = new ListNode(3);
head2->next->next->next = new ListNode(5);
head2->next->next->next->next = new ListNode(6);
head2->next->next->next->next->next = new ListNode(4);
head2->next->next->next->next->next->next = new ListNode(7);
ListNode* res2 = s.oddEvenList(head2);
printList(res2); // 输出:2 3 6 7 1 5 4
return 0;
}
关键细节解析
- 偶链表头节点保存 :必须用
evenHead记录偶链表的起始位置,因为遍历过程中even指针会不断后移,最终无法直接找到偶链表头部; - 遍历终止条件 :
while (even != nullptr && even->next != nullptr)- 若
even == nullptr:说明链表节点数为奇数,已遍历到最后一个奇数节点,无需继续; - 若
even->next == nullptr:说明链表节点数为偶数,已遍历到最后一个偶数节点,无需继续; - 此条件能避免空指针访问(
nullptr->next会崩溃);
- 若
- 指针重连逻辑 :
- 奇指针重连:
odd->next = even->next→ 跳过偶数节点,直接指向后续奇数节点; - 偶指针重连:
even->next = odd->next→ 跳过奇数节点,直接指向后续偶数节点;
- 奇指针重连:
- 空间复杂度 O (1) :全程仅使用了
odd、even、evenHead三个辅助指针,无额外数组 / 容器,满足题目要求; - 时间复杂度 O (n):每个节点仅被访问一次,遍历次数等于链表节点数 n。
为什么不推荐递归?
递归的本质是栈帧调用 ,对于 n 个节点的链表,递归会产生 n 个栈帧,额外空间复杂度为 O (n),直接违反题目 "O (1) 额外空间" 的硬性要求,因此这道题递归是错误解法。
测试结果
编译运行上述代码,会输出两个示例的正确结果:
1 3 5 2 4
2 3 6 7 1 5 4
总结
- 核心解法:双指针遍历 + 拆分重组,拒绝递归(空间不达标);
- 关键操作:保存偶链表头、正确的指针重连、安全的遍历终止条件;
- 复杂度:严格满足 O (n) 时间、O (1) 额外空间;
- 边界处理:空链表、单节点链表直接返回,无需处理。
我感觉就是双指针遍历,奇数指针一直在做跳过偶指针的操作,偶指针自立门户,奇数指针的最后一个连到偶指针的头就ok了。
写js写错了
javascript
/**
* 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 oddEvenList = function(head) {
if (head == null || head.next = null){
return head ;
}
let odd = head ;
let even = head.next ;
let evenHead = even ;
while(odd && odd.next){
odd.next = even.next;
odd = odd.next ;
even.next = odd.next ;
even = even.next ;
}
odd.next = evenHead;
return head ;
};
错误 1:边界判断的赋值错误(语法错误,直接报错)
javascript
// 错误代码
if (head == null || head.next = null) { ... }
// 正确代码
if (head == null || head.next === null) { ... }
问题 := 是赋值运算符 ,这里本意是判断是否相等 ,必须用相等运算符 ==(宽松相等)或===(严格相等);后果 :会把null赋值给head.next,直接破坏原链表结构,同时条件判断逻辑失效,触发语法 / 运行时错误。
错误 2:while 循环条件错误(逻辑错误,空指针 / 死循环)
javascript
// 错误代码
while(odd && odd.next) { ... }
// 正确代码
while(even && even.next) { ... }
核心问题 :遍历的终止条件判断对象错误,这是本题最关键的逻辑点;原因分析 :我们的遍历是通过偶数节点找下一个奇数节点 ,只有even和even.next都不为空时,才存在下一个奇数 / 偶数节点;
3 小结
我觉得进度有点慢了,每天一道题太少了,而且hot100 也都还没怎么做,真的做的太慢见得太少了,只要见过题解也都还不是很难啦!
针对性优化建议
- 提升刷题效率,增加单日刷题量 :基础思路理解无问题的前提下,可将单日刷题量提升至 2-3 道,优先选择同类型题目(如链表双指针:反转链表、环形链表、相交链表等),形成 "题型 + 解法" 的固定思维,强化同类问题的解题手感;
- 聚焦高频题库,优先刷 hot100 :hot100 是面试 / 算法考试的高频考点,覆盖了绝大多数核心算法思想(双指针、动态规划、回溯、贪心等),优先刷这类题能用最少的题量覆盖最多的核心考点,避免低效率的零散刷题;
- 刷题后增加 "复盘 + 默写" 环节 :每道题做完(尤其是踩坑题),花 5 分钟复盘核心思路、易错点、同类题关联 ,并尝试脱离题解默写代码,强化编码细节记忆,减少 "思路会、代码错" 的情况;
- 错题整理,重点攻克细节错误:建立错题本,记录编码中的基础错误(如语法、条件判断、指针操作),定期回顾,形成条件反射,避免重复踩坑。