O (1) 空间搞定链表:穿针引线法核心技巧与例题

文章目录

  • 穿针引线法的核心思想
  • 基础应用:链表反转
    • 1. 全链表反转
    • 2. 部分链表反转
  • 高级应用:链表重排
  • 穿针引线法的设计模式
  • 常见问题解决方案
    • 1. K个一组反转链表
    • 2. 环形链表检测

在链表操作的世界里,"穿针引线"是一种优雅而高效的技巧,它通过精准的指针操作,像缝纫一样重新连接节点,解决各种复杂的链表问题。这种技巧不依赖额外数据结构,空间复杂度仅为O(1),是算法面试中的必备技能。

穿针引线法的核心思想

  1. 指针即针线:使用2-3个辅助指针作为"针",通过修改节点的 next 指针来"缝合"链表
  2. 顺序是关键:操作顺序决定成败,必须先保存下一个节点再修改当前指针
  3. 边界防护:使用dummy节点处理头节点特殊情况
  4. 循环不变式:在循环中保持指针关系的稳定性

基础应用:链表反转

1. 全链表反转

ListNode* reverseList(ListNode* head) {
    ListNode* prev = nullptr;  // 前驱指针(针眼)
    ListNode* curr = head;     // 当前指针(针)
    
    while (curr) {
        ListNode* nextTemp = curr->next;  // 保存下一针位置
        curr->next = prev;     // 穿针:当前节点指向前驱
        prev = curr;           // 移动针眼
        curr = nextTemp;       // 移动针位置
    }
    
    return prev;  // 新链表头
}

反转过程可视化:

初始状态: 
prev → nullptr  
curr → [1][2][3]nullptr1针: 
[1]nullptr  
prev → [1]  
curr → [2]2针:
[2][1]nullptr  
prev → [2]  
curr → [3]

完成:
[3][2][1]nullptr

2. 部分链表反转

ListNode* reverseBetween(ListNode* head, int left, int right) {
    ListNode* dummy = new ListNode(0, head);  // 保护针(避免头节点特殊处理)
    ListNode* pre = dummy;
    
    // 定位反转起点
    for (int i = 0; i < left - 1; i++) {
        pre = pre->next;
    }
    
    // 穿针反转
    ListNode* curr = pre->next;
    for (int i = 0; i < right - left; i++) {
        ListNode* temp = curr->next;
        curr->next = temp->next;  // 跳过临时节点
        temp->next = pre->next;   // 头插法
        pre->next = temp;         // 重新链接
    }
    
    return dummy->next;
}

关键操作

  1. 定位子链表前驱位置(pre)
  2. 使用头插法逐步反转子链表
  3. 保持子链表与前后节点的连接

高级应用:链表重排

void reorderList(ListNode* head) {
    if (!head || !head->next) return;
    
    // Step1: 快慢指针找中点
    ListNode *slow = head, *fast = head->next;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    
    // Step2: 反转后半段
    ListNode* second = slow->next;
    slow->next = nullptr;  // 断开链表
    ListNode* prev = nullptr;
    while (second) {
        ListNode* temp = second->next;
        second->next = prev;
        prev = second;
        second = temp;
    }
    
    // Step3: 双指针交替合并
    ListNode* first = head;
    second = prev;
    while (second) {
        ListNode* temp1 = first->next;
        ListNode* temp2 = second->next;
        first->next = second;  // 穿针
        second->next = temp1;  // 引线
        first = temp1;
        second = temp2;
    }
}

三步策略

  1. 找中点:快指针走两步,慢指针走一步
  2. 反转后半段:标准反转操作
  3. 交替合并:双指针穿针引线连接节点

穿针引线法的设计模式

模式名称 指针组合 适用场景
三针模式 prev, curr, next 全链表反转
头插法模式 pre, curr, temp 部分反转/K组反转
双指针模式 slow, fast 找中点/环检测
分治模式 left, right 合并链表/重排链表
保护针模式 dummy + 其他指针 头节点可能变化的操作

常见问题解决方案

1. K个一组反转链表

ListNode* reverseKGroup(ListNode* head, int k) {
    ListNode* dummy = new ListNode(0, head);
    ListNode* pre = dummy, *end = dummy;
    
    while (end->next) {
        // 定位K个节点
        for (int i = 0; i < k && end; i++) 
            end = end->next;
        if (!end) break;
        
        // 穿针反转
        ListNode* start = pre->next;
        ListNode* nextGroup = end->next;
        end->next = nullptr;  // 断开分组
        
        pre->next = reverseList(start);  // 反转当前组
        start->next = nextGroup;         // 连接下一组
        
        // 移动指针
        pre = start;
        end = pre;
    }
    
    return dummy->next;
}

2. 环形链表检测

bool hasCycle(ListNode* head) {
    ListNode *slow = head, *fast = head;
    
    while (fast && fast->next) {
        slow = slow->next;         // 慢针走一步
        fast = fast->next->next;   // 快针走两步
        
        if (slow == fast)          // 针线相遇
            return true;
    }
    
    return false;
}

穿针引线法最佳实践

  1. 画图分析:在纸上画出指针变化过程
  2. 边界检查:总是检查空指针和单节点情况
  3. 逐步调试:使用小规模链表测试(3-5个节点)
  4. 模块化:将复杂操作分解为子函数(如反转、找中点)
  5. 循环不变式:明确每次循环前后指针的关系
// 循环不变式示例:反转链表
while (curr) {
    // 不变式:prev指向已反转部分,curr指向当前未反转部分
    ListNode* nextTemp = curr->next;
    curr->next = prev;  // 反转
    prev = curr;        // 维护不变式
    curr = nextTemp;    // 维护不变式
}

你可能感兴趣的:(算法,链表,c++,算法)