算法题 奇偶链表

奇偶链表

问题描述

给定单链表的头节点 head,把链表中的所有奇数位置节点(第1、3、5…个节点)放在一起,然后是所有偶数位置节点(第2、4、6…个节点),并返回重新排列后的链表。

注意:这里的奇偶指的是节点的位置(从1开始计数),而不是节点的值。

要求:

  • 空间复杂度应为 O(1)
  • 时间复杂度应为 O(n)
  • 必须保持奇数节点和偶数节点的相对顺序

示例

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

输入: 2->1->3->5->6->4->7->NULL  
输出: 2->3->6->7->1->5->4->NULL

算法思路

核心思想:分离-连接

  1. 分离:遍历链表,将奇数位置节点连接成一个链表,偶数位置节点连接成另一个链表
  2. 连接:将奇数链表的尾部连接到偶数链表的头部
  3. 关键:使用两个指针分别维护奇数链表和偶数链表,同时记录偶数链表的头节点

代码实现

方法一:双指针分离

/**
 * Definition for singly-linked list.
 */
class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

class Solution {
    /**
     * 奇偶链表重排:将奇数位置节点放在前面,偶数位置节点放在后面
     * 
     * 算法思路:
     * 1. 使用两个指针 odd 和 even 分别指向奇数位置和偶数位置的当前节点
     * 2. 保存 evenHead 作为偶数链表的起始位置
     * 3. 遍历过程中,odd 指向 odd.next.next,even 指向 even.next.next
     * 4. 最后将 odd 的尾部连接到 evenHead
     * 
     * @param head 链表头节点
     * @return 重排后的链表头节点
     * 
     * 时间复杂度:O(n),只需遍历一次链表
     * 空间复杂度:O(1),只使用常数额外空间
     */
    public ListNode oddEvenList(ListNode head) {
        // 边界情况:空链表或只有一个节点
        if (head == null || head.next == null) {
            return head;
        }
        
        // 初始化指针
        ListNode odd = head;           // 奇数位置指针,从第1个节点开始
        ListNode even = head.next;     // 偶数位置指针,从第2个节点开始
        ListNode evenHead = even;      // 保存偶数链表的头节点,用于最后连接
        
        // 遍历链表,分离奇偶节点
        // 继续条件:even != null 且 even.next != null
        // 因为 even 在 odd 后面,所以只需要检查 even 的情况
        while (even != null && even.next != null) {
            // 连接下一个奇数节点
            odd.next = even.next;
            odd = odd.next;            // 移动 odd 指针
            
            // 连接下一个偶数节点  
            even.next = odd.next;
            even = even.next;          // 移动 even 指针
        }
        
        // 连接奇数链表和偶数链表
        odd.next = evenHead;
        
        return head;  // 返回原头节点(奇数链表头)
    }
}

算法分析

时间复杂度

  • O(n):只需要遍历链表一次,每个节点访问一次

空间复杂度

  • O(1):只使用了固定数量的额外指针变量

算法过程

1->2->3->4->5->NULL

初始状态

  • odd = 1, even = 2, evenHead = 2
  • 链表:1->2->3->4->5->NULL

第一次循环

  • odd.next = 3 → 奇数链表:1->3
  • odd = 3
  • even.next = 4 → 偶数链表:2->4
  • even = 4
  • 当前状态:奇数链表 1->3,偶数链表 2->4->5->NULL

第二次循环

  • odd.next = 5 → 奇数链表:1->3->5
  • odd = 5
  • even.next = null → 偶数链表:2->4->NULL
  • even = null

循环结束

  • 连接:odd.next = evenHead5->2
  • 最终结果:1->3->5->2->4->NULL

测试用例

public class TestOddEvenList {
    
    /**
     * 创建链表
     */
    private static ListNode createList(int[] values) {
        if (values.length == 0) return null;
        ListNode head = new ListNode(values[0]);
        ListNode current = head;
        for (int i = 1; i < values.length; i++) {
            current.next = new ListNode(values[i]);
            current = current.next;
        }
        return head;
    }
    
    /**
     * 打印链表
     */
    private static String printList(ListNode head) {
        if (head == null) return "NULL";
        StringBuilder sb = new StringBuilder();
        ListNode current = head;
        while (current != null) {
            sb.append(current.val);
            if (current.next != null) sb.append("->");
            current = current.next;
        }
        sb.append("->NULL");
        return sb.toString();
    }
    
    public static void main(String[] args) {
        Solution solution = new Solution();
        
        // 测试用例1:标准示例
        ListNode head1 = createList(new int[]{1, 2, 3, 4, 5});
        ListNode result1 = solution.oddEvenList(head1);
        System.out.println("Test 1: " + printList(result1)); // 1->3->5->2->4->NULL
        
        // 测试用例2:偶数长度链表
        ListNode head2 = createList(new int[]{2, 1, 3, 5, 6, 4});
        ListNode result2 = solution.oddEvenList(head2);
        System.out.println("Test 2: " + printList(result2)); // 2->3->6->1->5->4->NULL
        
        // 测试用例3:单个节点
        ListNode head3 = createList(new int[]{1});
        ListNode result3 = solution.oddEvenList(head3);
        System.out.println("Test 3: " + printList(result3)); // 1->NULL
        
        // 测试用例4:两个节点
        ListNode head4 = createList(new int[]{1, 2});
        ListNode result4 = solution.oddEvenList(head4);
        System.out.println("Test 4: " + printList(result4)); // 1->2->NULL
        
        // 测试用例5:空链表
        ListNode head5 = null;
        ListNode result5 = solution.oddEvenList(head5);
        System.out.println("Test 5: " + printList(result5)); // NULL
        
        // 测试用例6:三个节点
        ListNode head6 = createList(new int[]{1, 2, 3});
        ListNode result6 = solution.oddEvenList(head6);
        System.out.println("Test 6: " + printList(result6)); // 1->3->2->NULL
        
        // 测试用例7:四个节点
        ListNode head7 = createList(new int[]{1, 2, 3, 4});
        ListNode result7 = solution.oddEvenList(head7);
        System.out.println("Test 7: " + printList(result7)); // 1->3->2->4->NULL
        
        // 测试用例8:包含重复值
        ListNode head8 = createList(new int[]{1, 1, 1, 1, 1});
        ListNode result8 = solution.oddEvenList(head8);
        System.out.println("Test 8: " + printList(result8)); // 1->1->1->1->1->NULL
    }
}

关键点

  1. 位置 vs 值

    • 要求按位置的奇偶性分组,不是按节点值的奇偶性
    • 第1个节点(位置1)是奇数位置,第2个节点(位置2)是偶数位置
  2. 指针移动顺序

    • 必须先更新 odd.next,再更新 even.next
    • 因为 even.next 依赖于更新后的 odd.next
  3. 循环终止条件

    • while (even != null && even.next != null)
    • 因为 evenodd 后面,所以检查 even 的情况就足够了
    • 如果 even 为 null,说明已经到达链表末尾
    • 如果 even.next 为 null,说明下一个奇数位置不存在
  4. 边界处理

    • 空链表:直接返回 null
    • 单节点链表:直接返回原链表

常见问题

  1. 为什么循环条件只检查 even 而不检查 odd?

    • 因为 even 总是在 odd 的后面,如果 even 还存在,那么 odd 肯定也存在
    • 当 even 为 null 或 even.next 为 null 时,说明没有更多的偶数或奇数节点需要处理
  2. 为什么要保存 evenHead?

    • 因为在遍历过程中,even 指针会不断移动,最终指向偶数链表的尾部
    • 需要保存最初的偶数链表头部,用于最后将奇数链表连接到偶数链表
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值