链表经典面试题目

链表节点定义(所有题目通用)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 定义链表节点
struct ListNode {
    int val;
    struct ListNode *next;
};

1. 删除链表中等于给定值 val 的所有结点

题目描述

删除链表中所有值为 val 的节点,返回修改后的链表头。

核心思路

• 虚拟头节点法:创建一个虚拟头节点 dummy,让它的 next 指向原链表头。这样可以统一处理头节点本身就是待删除节点的情况。

• 遍历链表,当发现当前节点的下一个节点值为 val 时,直接调整指针跳过该节点,并释放内存。

复杂度分析

• 时间复杂度:O(n),需要遍历整个链表一次。

• 空间复杂度:O(1),只使用了常数级的额外空间。

cpp 复制代码
struct ListNode* removeElements(struct ListNode* head, int val) {
    // 创建虚拟头节点,避免单独处理头节点被删除的情况
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummy->next = head;  // 虚拟头节点指向原链表头
    struct ListNode* cur = dummy;  // 用cur指针遍历链表

    // 遍历链表,直到cur的下一个节点为空
    while (cur->next != NULL) {
        // 如果下一个节点的值等于val
        if (cur->next->val == val) {
            struct ListNode* temp = cur->next;  // 保存待删除节点
            cur->next = cur->next->next;        // 跳过待删除节点
            free(temp);                         // 释放待删除节点的内存
        } else {
            cur = cur->next;  // 否则,cur指针后移
        }
    }

    head = dummy->next;  // 新的头节点是虚拟头节点的下一个节点
    free(dummy);         // 释放虚拟头节点的内存
    return head;         // 返回新的头节点
}

2. 反转一个单链表

题目描述

反转单链表,返回新的链表头。

核心思路

• 双指针迭代法:用 pre 保存前一个节点,cur 指向当前节点。每次循环中,先保存 cur->next,再将 cur->next 指向 pre,然后更新 pre 和 cur。

• 也可以用递归法,递归到链表末尾后反向调整指针。

复杂度分析

• 时间复杂度:O(n),每个节点被访问一次。

• 空间复杂度:O(1)(迭代法),递归法为 O(n)(递归栈深度)。

cpp 复制代码
// 反转单链表,返回反转后的新头节点
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* pre = NULL;  // 前驱节点:记录cur的前一个节点,初始为NULL(原链表头的前驱是空)
    struct ListNode* cur = head;  // 当前节点:从原链表头开始遍历

    // 遍历链表,cur走到NULL时结束(所有节点都完成反转)
    while (cur != NULL) {
        // 【关键】先保存cur的下一个节点,否则反转后会丢失原链表的后续节点
        struct ListNode* next = cur->next;
        cur->next = pre;                    // 反转cur的next指针,指向pre(完成当前节点的反转)
        pre = cur;                          // pre向后移动,更新为当前的cur(为下一个节点反转做准备)
        cur = next;                         // cur向后移动,更新为之前保存的next(继续遍历下一个节点)
    }
    return pre;  // 遍历结束,cur为NULL,pre停在原链表最后一个节点,即反转后新链表的头节点
}

核心步骤图解(以链表 1->2->3->NULL 为例)

  1. 初始状态:pre=NULL,cur=1,next未定义

  2. 第一次循环:

next=2 → 1->next=NULL → pre=1 → cur=2

链表变为:NULL<-1 2->3->NULL

  1. 第二次循环:

next=3 → 2->next=1 → pre=2 → cur=3

链表变为:NULL<-1<-2 3->NULL

  1. 第三次循环:

next=NULL → 3->next=2 → pre=3 → cur=NULL

链表变为:NULL<-1<-2<-3

  1. 循环结束:返回pre=3,即新头节点,反转完成。

代码关键避坑点

  1. 必须先保存next:如果先执行cur->next=pre,会直接丢失原cur->next的地址,无法继续遍历后续节点,这是新手最容易犯的错;

  2. pre初始化为NULL:原链表的头节点反转后是尾节点,尾节点的next必须为NULL,符合链表规范;

  3. 返回pre而非cur:循环结束时cur=NULL,pre才是反转后新链表的有效头节点。

极简版写法

保留核心逻辑,面试时快速手写,编译器可正常运行:

cpp 复制代码
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode *pre = NULL, *cur = head, *next;
    while (cur) {
        next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}

3. 返回链表的中间结点

题目描述

给定非空单链表,返回中间节点。如果有两个中间节点,返回第二个。

核心思路

• 快慢指针法:快指针 fast 每次走2步,慢指针 slow 每次走1步。当快指针走到链表末尾时,慢指针正好指向中间节点。

• 例如链表长度为偶数时,快指针会停在最后一个节点的 next(即 NULL),此时慢指针指向第二个中间节点。

复杂度分析

• 时间复杂度:O(n),快指针最多走 n/2 步,慢指针同步移动。

• 空间复杂度:O(1)。

cpp 复制代码
struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* slow = head;  // 慢指针,每次走1步
    struct ListNode* fast = head;  // 快指针,每次走2步

    // 快指针没走到末尾时继续循环
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;         // 慢指针走1步
        fast = fast->next->next;   // 快指针走2步
    }
    return slow;  // 快指针到末尾时,慢指针指向中间节点
}

4. 输出链表中倒数第 k 个结点

题目描述

输入一个链表,输出该链表中倒数第 k 个节点。

核心思路

• 快慢指针法:

  1. 先让快指针 fast 向前走 k 步。

  2. 然后快慢指针同时向前移动,直到快指针走到链表末尾。

  3. 此时慢指针 slow 指向的就是倒数第 k 个节点。

复杂度分析

• 时间复杂度:O(n),最多遍历链表两次。

• 空间复杂度:O(1)。

cpp 复制代码
struct ListNode* getKthFromEnd(struct ListNode* head, int k) {
    struct ListNode* fast = head;  // 快指针
    struct ListNode* slow = head;  // 慢指针

    // 先让快指针走k步
    for (int i = 0; i < k; i++) {
        fast = fast->next;
    }

    // 快慢指针同时走,直到快指针到末尾
    while (fast != NULL) {
        fast = fast->next;
        slow = slow->next;
    }

    return slow;  // 此时慢指针指向倒数第k个节点
}

5. 合并两个有序链表

题目描述

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

核心思路

• 虚拟头节点法:创建一个虚拟头节点 dummy,用 cur 指针遍历两个输入链表,每次选择较小的节点加入新链表。

• 当其中一个链表遍历完后,直接将另一个链表的剩余部分接在新链表末尾。

复杂度分析

• 时间复杂度:O(m+n),其中 m 和 n 是两个链表的长度。

• 空间复杂度:O(1),只使用了常数级的额外空间。

cpp 复制代码
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    // 创建虚拟头节点,方便统一处理新链表的插入
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* cur = dummy;  // cur指针用于构建新链表

    // 当两个链表都不为空时
    while (list1 != NULL && list2 != NULL) {
        // 选择值较小的节点加入新链表
        if (list1->val < list2->val) {
            cur->next = list1;
            list1 = list1->next;
        } else {
            cur->next = list2;
            list2 = list2->next;
        }
        cur = cur->next;  // cur指针后移
    }

    // 把剩下的节点直接接到新链表末尾
    cur->next = list1 != NULL ? list1 : list2;
    struct ListNode* res = dummy->next;  // 新链表的头节点
    free(dummy);  // 释放虚拟头节点
    return res;
}

6. 以给定值 x 为基准分割链表

题目描述

编写代码,以给定值 x 为基准将链表分割成两部分,所有小于 x 的节点排在大于或等于 x 的节点之前。

核心思路

• 拆分拼接法:

  1. 创建两个虚拟头节点 dummy1(存储小于 x 的节点)和 dummy2(存储大于等于 x 的节点)。

  2. 遍历原链表,将节点分别插入到两个虚拟链表中。

  3. 最后将 dummy2 的链表接在 dummy1 的链表末尾,并将 dummy2 链表的尾节点 next 置为 NULL(避免成环)。

复杂度分析

• 时间复杂度:O(n),遍历一次原链表。

• 空间复杂度:O(1),只使用了常数级的额外空间。

cpp 复制代码
struct ListNode* partition(struct ListNode* head, int x) {
    // 创建两个虚拟头节点,分别存储小于x和大于等于x的节点
    struct ListNode* dummy1 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* dummy2 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* cur1 = dummy1;  // 指向小于x的链表
    struct ListNode* cur2 = dummy2;  // 指向大于等于x的链表

    // 遍历原链表
    while (head != NULL) {
        if (head->val < x) {
            cur1->next = head;  // 加入小于x的链表
            cur1 = cur1->next;
        } else {
            cur2->next = head;  // 加入大于等于x的链表
            cur2 = cur2->next;
        }
        head = head->next;  // 原链表指针后移
    }

    cur2->next = NULL;  // 防止链表成环
    cur1->next = dummy2->next;  // 拼接两个链表
    struct ListNode* res = dummy1->next;  // 新链表的头节点
    free(dummy1);  // 释放虚拟头节点
    free(dummy2);
    return res;
}

7. 链表的回文结构

题目描述

判断一个链表是否为回文链表。

核心思路

• 三步法:

  1. 找中间节点:用快慢指针找到链表的中间节点。

  2. 反转后半部分:反转中间节点之后的链表。

  3. 比较前后两部分:同时遍历前半部分和反转后的后半部分,比较节点值是否相同。

  4. (可选)恢复链表:如果题目要求不修改原链表,可再次反转后半部分并拼接回原链表。

复杂度分析

• 时间复杂度:O(n),找中间节点、反转、比较各需 O(n) 时间。

• 空间复杂度:O(1),只使用了常数级的额外空间。

cpp 复制代码
bool isPalindrome(struct ListNode* head) {
    // 空链表或只有一个节点时,直接是回文
    if (head == NULL || head->next == NULL) return true;

    // 1. 快慢指针找中间节点
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (fast->next != NULL && fast->next->next != NULL) {
        slow = slow->next;         // 慢指针走1步
        fast = fast->next->next;   // 快指针走2步
    }

    // 2. 反转后半部分链表
    struct ListNode* pre = NULL;
    struct ListNode* cur = slow->next;
    while (cur != NULL) {
        struct ListNode* next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }

    // 3. 比较前后两部分
    struct ListNode* p1 = head;
    struct ListNode* p2 = pre;
    bool res = true;
    while (res && p2 != NULL) {
        if (p1->val != p2->val) res = false;
        p1 = p1->next;
        p2 = p2->next;
    }

    // 4. 恢复原链表(可选,面试中如果不要求可以省略)
    cur = pre;
    pre = NULL;
    while (cur != NULL) {
        struct ListNode* next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }
    slow->next = pre;

    return res;
}

8. 找出两个链表的第一个公共结点

题目描述

输入两个链表,找出它们的第一个公共节点。

核心思路

• 双指针法:

  1. 两个指针 pA 和 pB 分别从两个链表头出发。

  2. 当 pA 走到链表末尾时,将其指向另一个链表的头节点;同理 pB 走到末尾时也指向另一个链表的头节点。

  3. 两个指针会在第一个公共节点处相遇(如果存在公共节点),或者同时走到 NULL(如果没有公共节点)。

复杂度分析

• 时间复杂度:O(m+n),其中 m 和 n 是两个链表的长度。

• 空间复杂度:O(1)。

cpp 复制代码
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* pA = headA;
    struct ListNode* pB = headB;

    // 当两个指针不相等时继续循环
    while (pA != pB) {
        // 如果pA走到末尾,就跳到另一个链表的头
        pA = pA != NULL ? pA->next : headB;
        // 如果pB走到末尾,就跳到另一个链表的头
        pB = pB != NULL ? pB->next : headA;
    }
    // 相遇时就是第一个公共节点,或者都为NULL(没有公共节点)
    return pA;
}

方法二核心思路总结

  1. 判断是否有公共节点:两个链表如果有公共节点,它们的尾节点一定是同一个,所以先遍历到尾节点进行判断。

  2. 消除长度差:计算两个链表的长度差,让长链表的指针先移动 gap 步,使两个指针从相同长度的位置开始遍历。

  3. 同步遍历找交点:两个指针同时移动,第一次相遇的节点就是第一个公共节点。

cpp 复制代码
// 寻找两个链表的第一个公共节点
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    // 定义两个指针,分别指向两个链表的头节点
    struct ListNode* curA = headA, *curB = headB;
    // 初始化两个链表的长度,初始为1是因为cur已经指向了头节点
    int lena = 1, lenb = 1;

    // 遍历链表A,计算链表A的长度
    while (curA->next) {
        curA = curA->next;
        lena++;
    }

    // 遍历链表B,计算链表B的长度
    while (curB->next) {
        curB = curB->next;
        lenb++;
    }

    // 如果两个链表的尾节点不相同,说明没有公共节点,直接返回NULL
    if (curA != curB) return NULL;

    // 计算两个链表的长度差
    int gap = abs(lena - lenb);
    // 先默认链表A是长链表,链表B是短链表
    struct ListNode* longlist = headA, *shortlist = headB;

    // 如果链表B更长,就交换longlist和shortlist的指向
    if (lenb > lena) {
        longlist = headB;
        shortlist = headA;
    }

    // 让长链表的指针先移动gap步,使两个指针处于"同一起跑线"
    while (gap--) {
        longlist = longlist->next;
    }

    // 两个指针同时向后移动,直到相遇,相遇点就是第一个公共节点
    while (longlist != shortlist) {
        longlist = longlist->next;
        shortlist = shortlist->next;
    }

    // 返回相遇的节点(也可以返回longlist)
    return shortlist;
}

9. 带随机指针链表的深度拷贝

题目描述

复制一个包含next指针和random随机指针的链表,新链表是完全独立的深度拷贝,新节点的next和random都要指向新链表的对应节点,而非原链表节点,且不能修改原链表结构。

核心思路

  1. 第一次遍历:创建原链表每个节点的拷贝节点,用哈希表(数组模拟,C语言无原生哈希表) 建立原节点→拷贝节点的映射关系,同时完成拷贝节点val的赋值和next的初步连接。

  2. 第二次遍历:通过哈希表的映射,给拷贝节点的random指针赋值(原节点的random指向A,拷贝节点的random就指向A的拷贝节点)。

  3. 最终返回拷贝链表的头节点,实现完全独立的深度拷贝。

cpp 复制代码
// 深度拷贝带随机指针的链表:原地拼接+拆分法(空间复杂度O(1),时间O(n))
struct Node* copyRandomList(struct Node* head) {
    // 定义遍历指针cur,初始指向原链表头节点
    struct Node* cur = head;

    // 第一步:拷贝每个原节点,将拷贝节点插入到原节点的正后方
    while (cur) {
        // 为拷贝节点分配内存,创建新节点
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;        // 拷贝原节点的值
        copy->next = cur->next;      // 拷贝节点next先指向原节点的下一个节点
        cur->next = copy;            // 原节点next指向拷贝节点,完成插入
        cur = copy->next;            // 跳过拷贝节点,cur移到下一个原节点
    }

    // 第二步:为拷贝节点的random指针赋值,利用拼接结构直接映射
    cur = head;                      // 重置cur到原链表头,重新遍历
    while (cur) {
        struct Node* copy = cur->next;   // copy指向当前原节点的拷贝节点
        if (cur->random == NULL) {       // 原节点random为NULL
            copy->random = NULL;         // 拷贝节点random也置为NULL
        } else {
            // 核心:原节点random的下一个节点,就是其拷贝节点
            copy->random = cur->random->next;
        }
        cur = copy->next;                // 跳过拷贝节点,移到下一个原节点
    }

    // 第三步:拆分链表------将拷贝节点单独拆出成新链表,同时恢复原链表结构
    struct Node* copyhead = NULL, *copytail = NULL;  // 新链表的头/尾指针(尾插法)
    cur = head;                                      // 重置cur到原链表头
    while (cur) {
        struct Node* copy = cur->next;       // copy指向当前原节点的拷贝节点
        struct Node* next = copy->next;      // next保存原链表的下一个原节点(防止丢失)
        
        // 尾插法构建拷贝节点的新链表
        if (copytail == NULL) {              // 新链表为空时,初始化头/尾指针
            copyhead = copytail = copy;
        } else {                             // 新链表非空时,尾插并更新尾指针
            copytail->next = copy;
            copytail = copytail->next;
        }

        cur->next = next;    // 恢复原节点的next指针,指向原本的下一个原节点
        cur = next;          // cur移到下一个原节点,继续拆分
    }

    // 返回拷贝链表的头节点,完成深度拷贝
    return copyhead;
}

进阶优化思路(原地拼接法,空间复杂度O(1))

如果要求空间复杂度为O(1)(不使用哈希表),可以用原地拼接+拆分法,核心步骤:

  1. 遍历原链表,在每个原节点后插入其拷贝节点(原1→拷贝1→原2→拷贝2→...)。

  2. 赋值拷贝节点的random:拷贝节点->random = 原节点->random->next(原节点random的下一个就是其拷贝)。

  3. 拆分链表:将原节点和拷贝节点分离,恢复原链表,同时形成独立的拷贝链表。

关键注意点

  1. 深度拷贝必须为新节点分配独立内存,不能直接赋值指针(否则只是浅拷贝,指向原链表节点)。

  2. 处理random指针时,一定要先判断原节点random是否为NULL,避免空指针访问崩溃。

  3. 原地拼接法最后必须拆分并恢复原链表,这是面试的隐含要求(不能修改输入原链表)。

原地拼接法带注释代码

cpp 复制代码
struct Node* copyRandomList(struct Node* head) {
    if (head == NULL) return NULL;

    // 步骤1:原地拼接,原节点后插入拷贝节点
    struct Node* cur = head;
    while (cur != NULL) {
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;
        copy->next = cur->next;  // 拷贝节点next指向原节点的下一个
        cur->next = copy;        // 原节点next指向拷贝节点
        cur = copy->next;        // 跳过拷贝节点,遍历下一个原节点
    }

    // 步骤2:赋值拷贝节点的random指针
    cur = head;
    while (cur != NULL) {
        struct Node* copy = cur->next;
        // 原节点random不为NULL时,拷贝节点random指向原random的拷贝
        copy->random = cur->random == NULL ? NULL : cur->random->next;
        cur = copy->next;        // 跳过拷贝节点
    }

    // 步骤3:拆分链表,恢复原链表,生成拷贝链表
    cur = head;
    struct Node* newHead = head->next;  // 拷贝链表头为第一个拷贝节点
    while (cur != NULL) {
        struct Node* copy = cur->next;
        cur->next = copy->next;         // 原节点next恢复为原下一个节点
        // 拷贝节点next:如果原节点下一个不为NULL,指向其拷贝,否则为NULL
        copy->next = cur->next == NULL ? NULL : cur->next->next;
        cur = cur->next;                // 遍历原链表下一个节点
    }

    return newHead;
}

10.判断链表是否有环

核心解法:快慢指针法(Floyd判圈算法)

空间复杂度O(1)(仅用两个指针)、时间复杂度O(n),面试最优解,核心逻辑:慢指针每次走1步,快指针每次走2步,若链表有环,快指针必会在环内追上慢指针;若无环,快指针会先走到NULL。

代码避坑关键细节

  1. 循环条件:必须同时判断fast != NULL && fast->next != NULL,否则快指针会出现fast->next->next空指针访问崩溃;

  2. 指针初始化:快慢指针都从head开始,不要让fast初始为head->next,会导致相遇判断出错;

  3. 边界处理:直接过滤空链表和单节点链表,这类情况不可能有环。

cpp 复制代码
// 判断链表是否有环,有环返回1,无环返回0
int hasCycle(struct ListNode *head) {
    // 边界条件:空链表/只有一个节点,直接无环
    if (head == NULL || head->next == NULL) {
        return 0;
    }
    // 初始化快慢指针,均从表头出发(避免初始相遇逻辑混乱)
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    // 循环条件:fast和fast->next非空,防止空指针解引用
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;       // 慢指针走1步
        fast = fast->next->next; // 快指针走2步
        if (slow == fast) {      // 快慢指针相遇,说明有环
            return 1;
        }
    }
    // 循环结束未相遇,快指针走到链表末尾,无环
    return 0;
}

11. 寻找链表环的入口节点

题目描述

判断单链表是否有环,若有环返回入环的第一个节点;若无环返回NULL,要求空间复杂度O(1)(仅用指针,不借助哈希表),这是面试的核心考点。

核心思路

  1. 先判断链表是否有环

• 慢指针slow每次走1步,快指针fast每次走2步,同时从表头出发。

• 若链表无环:快指针会先走到NULL,直接返回NULL。

• 若链表有环:快慢指针一定会在环内相遇(快指针绕环追上慢指针)。

  1. 再找环的入口节点(核心推导)

• 相遇后,将其中一个指针重置到表头,另一个指针留在相遇点。

• 两个指针同时以1步/次的速度前进,第一次相遇的节点就是环的入口节点。

cpp 复制代码
// 寻找链表环的入口节点,无环返回NULL
struct ListNode *detectCycle(struct ListNode *head) {
    // 边界条件:空链表或只有一个节点,无环直接返回NULL
    if (head == NULL || head->next == NULL) {
        return NULL;
    }

    // 初始化快慢指针,均从表头出发(避免初始相遇问题)
    struct ListNode* slow = head;
    struct ListNode* fast = head;

    // 第一步:快慢指针遍历,判断是否有环
    // 循环条件:fast和fast->next非空(防止fast->next->next空指针访问)
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;       // 慢指针走1步
        fast = fast->next->next; // 快指针走2步
        // 快慢指针相遇,说明有环,跳出循环找入口
        if (slow == fast) {
            break;
        }
    }

    // 循环结束后,若快慢指针未相遇,说明无环(fast走到链表末尾)
    if (slow != fast) {
        return NULL;
    }

    // 第二步:找环的入口------将慢指针重置到表头,快慢指针均走1步
    slow = head;
    while (slow != fast) {
        slow = slow->next;
        fast = fast->next;
    }

    // 相遇点即为环的入口节点,返回即可
    return slow;
}

关键推导

设:表头到环入口的距离为a,环入口到相遇点的距离为b,相遇点绕环回到入口的距离为c,环总长度L = b + c。

• 相遇时,慢指针走了 a + b 步,快指针走了 a + b + n*L 步(n为快指针绕环的圈数)。

• 因快指针速度是慢指针2倍,故 2*(a + b) = a + b + n*L → 化简得 a = n*L - b → a = (n-1)*L + c。

• 含义:表头到入口的距离a = 相遇点绕环走(n-1)圈再走c步的距离,因此两指针同速前进必会在入口相遇。

代码关键细节

  1. 循环条件:必须判断fast != NULL && fast->next != NULL,否则快指针会出现fast->next->next空指针解引用崩溃。

  2. 指针初始化:快慢指针均从head出发,而非slow=head、fast=head->next(会导致相遇判断逻辑混乱)。

  3. 无环判断:循环结束后必须校验slow == fast,否则快指针走到末尾时直接返回NULL。

相关推荐
Hcoco_me2 小时前
大模型面试题90:half2,float4这种优化 与 pack优化的底层原理是什么?
人工智能·算法·机器学习·langchain·vllm
Python算法实战2 小时前
《大模型面试宝典》(2026版) 正式发布!
人工智能·深度学习·算法·面试·职场和发展·大模型
石像鬼₧魂石2 小时前
Windows Server 2003 域控制器靶机搭建与渗透环境配置手册
linux·windows·学习
啥都会点的大秀2 小时前
声学仿真学习笔记
笔记·学习
czwxkn3 小时前
数据结构-线性表
数据结构
tobias.b3 小时前
408真题解析-2010-1-数据结构-栈基础操作
数据结构·408真题解析
好奇龙猫3 小时前
【AI学习-comfyUI学习-三十六节-黑森林-融合+扩图工作流-各个部分学习】
人工智能·学习
菜鸟233号3 小时前
力扣213 打家劫舍II java实现
java·数据结构·算法·leetcode
方便面不加香菜3 小时前
数据结构--栈和队列
c语言·数据结构