链表是数据结构中的重要内容,也是面试中的高频考点。本文将从链表的基础知识出发,逐步深入,结合经典题目和代码实现,帮助你全面掌握链表的操作技巧。


链表的理论基础

链表的种类

链表主要分为以下几种:

  1. 单链表:每个节点包含数据和指向下一个节点的指针。
  2. 双链表:每个节点包含数据、指向前一个节点的指针和指向下一个节点的指针。
  3. 循环链表:尾节点指向头节点,形成一个环。

链表的存储方式

链表的节点在内存中是分散存储的,通过指针连接在一起。与数组不同,链表不需要连续的内存空间。

链表的操作

链表的核心操作包括:

  • :在链表中插入节点。
  • :删除链表中的节点。
  • :修改链表中的节点值。
  • :查找链表中的节点。

数组与链表的性能对比

  • 数组:适合随机访问,插入和删除效率低。
  • 链表:适合频繁插入和删除,随机访问效率低。

链表经典题目

1. 虚拟头结点

虚拟头结点是链表操作中的一个重要技巧,可以简化对头结点的处理。

代码实现

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

class MyLinkedList {
private:
    ListNode* dummyHead; // 虚拟头结点
    int size; // 链表长度

public:
    MyLinkedList() {
        dummyHead = new ListNode(0); // 初始化虚拟头结点
        size = 0;
    }

    // 获取第index个节点的值
    int get(int index) {
        if (index < 0 || index >= size) return -1;
        ListNode* cur = dummyHead->next;
        while (index--) cur = cur->next;
        return cur->val;
    }

    // 在链表头部插入节点
    void addAtHead(int val) {
        ListNode* newNode = new ListNode(val);
        newNode->next = dummyHead->next;
        dummyHead->next = newNode;
        size++;
    }

    // 在链表尾部插入节点
    void addAtTail(int val) {
        ListNode* newNode = new ListNode(val);
        ListNode* cur = dummyHead;
        while (cur->next) cur = cur->next;
        cur->next = newNode;
        size++;
    }

    // 在第index个节点前插入节点
    void addAtIndex(int index, int val) {
        if (index > size) return;
        ListNode* newNode = new ListNode(val);
        ListNode* cur = dummyHead;
        while (index--) cur = cur->next;
        newNode->next = cur->next;
        cur->next = newNode;
        size++;
    }

    // 删除第index个节点
    void deleteAtIndex(int index) {
        if (index < 0 || index >= size) return;
        ListNode* cur = dummyHead;
        while (index--) cur = cur->next;
        ListNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        size--;
    }
};

2. 反转链表

反转链表是链表操作中的经典问题,常见解法有迭代法和递归法。

迭代法

ListNode* reverseList(ListNode* head) {
    ListNode* prev = nullptr;
    ListNode* cur = head;
    while (cur) {
        ListNode* next = cur->next;
        cur->next = prev;
        prev = cur;
        cur = next;
    }
    return prev;
}

递归法

ListNode* reverseList(ListNode* head) {
    if (!head || !head->next) return head;
    ListNode* newHead = reverseList(head->next);
    head->next->next = head;
    head->next = nullptr;
    return newHead;
}

3. 删除倒数第N个节点

使用双指针技巧可以高效删除倒数第N个节点。

代码实现

ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* dummyHead = new ListNode(0);
    dummyHead->next = head;
    ListNode* fast = dummyHead;
    ListNode* slow = dummyHead;

    // 快指针先走n步
    while (n-- && fast) fast = fast->next;

    // 快慢指针一起走
    while (fast->next) {
        fast = fast->next;
        slow = slow->next;
    }

    // 删除节点
    ListNode* tmp = slow->next;
    slow->next = slow->next->next;
    delete tmp;

    return dummyHead->next;
}

4. 链表相交

找到两个链表的交点(内存地址相同的节点)。

代码实现

ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    ListNode* curA = headA;
    ListNode* curB = headB;

    // 计算两个链表的长度
    int lenA = 0, lenB = 0;
    while (curA) { lenA++; curA = curA->next; }
    while (curB) { lenB++; curB = curB->next; }

    // 长链表先走
    curA = headA;
    curB = headB;
    if (lenA > lenB) {
        int gap = lenA - lenB;
        while (gap--) curA = curA->next;
    } else {
        int gap = lenB - lenA;
        while (gap--) curB = curB->next;
    }

    // 一起走,直到相遇
    while (curA != curB) {
        curA = curA->next;
        curB = curB->next;
    }
    return curA;
}

5. 环形链表

判断链表是否有环,并找到环的入口。

代码实现

ListNode* detectCycle(ListNode* head) {
    ListNode* slow = head;
    ListNode* fast = head;

    // 判断是否有环
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) break;
    }

    // 无环
    if (!fast || !fast->next) return nullptr;

    // 找到环的入口
    slow = head;
    while (slow != fast) {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

总结

链表的核心在于指针的操作,熟练掌握链表的基础操作和经典题目是面试中的关键。本文从链表的基础知识出发,详细讲解了虚拟头结点、反转链表、删除倒数第N个节点、链表相交和环形链表等经典问题,并提供了完整的C++代码实现。希望这篇文章能帮助你彻底掌握链表!

如果你有任何问题或建议,欢迎在评论区留言!