Leetcode 110 奇偶链表

1 题目

328. 奇偶链表

给定单链表的头节点 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. 索引定义 :题目中第一个节点索引为奇数,第二个为偶数,依此类推(节点位置从 1 开始计数);
  2. 双指针分工 :用odd指针指向奇数索引节点even指针指向偶数索引节点,各自遍历对应分组的节点;
  3. 链表拆分 :遍历过程中,将奇数节点和偶数节点分别串联成两个独立的子链表(奇链表、偶链表),保持原相对顺序;
  4. 链表重组:遍历结束后,将偶链表直接连接到奇链表的尾部,完成最终重组。

完整 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;
}

关键细节解析

  1. 偶链表头节点保存 :必须用evenHead记录偶链表的起始位置,因为遍历过程中even指针会不断后移,最终无法直接找到偶链表头部;
  2. 遍历终止条件while (even != nullptr && even->next != nullptr)
    • even == nullptr:说明链表节点数为奇数,已遍历到最后一个奇数节点,无需继续;
    • even->next == nullptr:说明链表节点数为偶数,已遍历到最后一个偶数节点,无需继续;
    • 此条件能避免空指针访问(nullptr->next会崩溃);
  3. 指针重连逻辑
    • 奇指针重连:odd->next = even->next → 跳过偶数节点,直接指向后续奇数节点;
    • 偶指针重连:even->next = odd->next → 跳过奇数节点,直接指向后续偶数节点;
  4. 空间复杂度 O (1) :全程仅使用了oddevenevenHead三个辅助指针,无额外数组 / 容器,满足题目要求;
  5. 时间复杂度 O (n):每个节点仅被访问一次,遍历次数等于链表节点数 n。

为什么不推荐递归?

递归的本质是栈帧调用 ,对于 n 个节点的链表,递归会产生 n 个栈帧,额外空间复杂度为 O (n),直接违反题目 "O (1) 额外空间" 的硬性要求,因此这道题递归是错误解法

测试结果

编译运行上述代码,会输出两个示例的正确结果:

复制代码
1 3 5 2 4 
2 3 6 7 1 5 4 

总结

  1. 核心解法:双指针遍历 + 拆分重组,拒绝递归(空间不达标);
  2. 关键操作:保存偶链表头、正确的指针重连、安全的遍历终止条件;
  3. 复杂度:严格满足 O (n) 时间、O (1) 额外空间;
  4. 边界处理:空链表、单节点链表直接返回,无需处理。

我感觉就是双指针遍历,奇数指针一直在做跳过偶指针的操作,偶指针自立门户,奇数指针的最后一个连到偶指针的头就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) { ... }

核心问题 :遍历的终止条件判断对象错误,这是本题最关键的逻辑点;原因分析 :我们的遍历是通过偶数节点找下一个奇数节点 ,只有eveneven.next都不为空时,才存在下一个奇数 / 偶数节点;

3 小结

我觉得进度有点慢了,每天一道题太少了,而且hot100 也都还没怎么做,真的做的太慢见得太少了,只要见过题解也都还不是很难啦!

针对性优化建议
  1. 提升刷题效率,增加单日刷题量 :基础思路理解无问题的前提下,可将单日刷题量提升至 2-3 道,优先选择同类型题目(如链表双指针:反转链表、环形链表、相交链表等),形成 "题型 + 解法" 的固定思维,强化同类问题的解题手感;
  2. 聚焦高频题库,优先刷 hot100 :hot100 是面试 / 算法考试的高频考点,覆盖了绝大多数核心算法思想(双指针、动态规划、回溯、贪心等),优先刷这类题能用最少的题量覆盖最多的核心考点,避免低效率的零散刷题;
  3. 刷题后增加 "复盘 + 默写" 环节 :每道题做完(尤其是踩坑题),花 5 分钟复盘核心思路、易错点、同类题关联 ,并尝试脱离题解默写代码,强化编码细节记忆,减少 "思路会、代码错" 的情况;
  4. 错题整理,重点攻克细节错误:建立错题本,记录编码中的基础错误(如语法、条件判断、指针操作),定期回顾,形成条件反射,避免重复踩坑。
相关推荐
踩坑记录7 小时前
leetcode hot100 easy 101. 对称二叉树 递归 层序遍历 bfs
算法·leetcode·宽度优先
大雷神7 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地-- 第24篇:学习中心 - 课程体系设计
大数据·学习·harmonyos
2501_940315268 小时前
leetcode182动态口令(将字符的前几个元素放在字符串后面)
算法
老鼠只爱大米8 小时前
LeetCode经典算法面试题 #98:验证二叉搜索树(递归法、迭代法等五种实现方案详解)
算法·leetcode·二叉树·递归·二叉搜索树·迭代
疯狂的喵14 小时前
C++编译期多态实现
开发语言·c++·算法
scx2013100414 小时前
20260129LCA总结
算法·深度优先·图论
2301_7657031414 小时前
C++中的协程编程
开发语言·c++·算法
m0_7487080514 小时前
实时数据压缩库
开发语言·c++·算法
小白郭莫搞科技14 小时前
鸿蒙跨端框架Flutter学习:CustomTween自定义Tween详解
学习·flutter·harmonyos