开篇介绍:
hello 大家,前几篇博客里,我们一起攻克了不少力扣上的经典链表题,一路跟着练下来的小伙伴们应该都有体会,那些题目大多是稍有难度的综合题,很能锻炼对链表操作的灵活运用。
不过呢,学东西总得循序渐进,基础打牢了,后面才能走得更稳。正巧牛客网里也藏着不少链表基础题,这些题不玩太多花哨的技巧,更贴近初学者的入门节奏,特别适合用来巩固指针操作、边界处理这些核心基本功。
所以在本篇博客中,我们就来好好解析这些题目,把基础再捋得顺顺当当的。
值得一提的是,等这篇内容结束后,我对链表基础题的解析就暂告一段落啦。大家可以期待一下接下来的内容会是什么 —— 咱们很快再见~
下面我们依旧是老样子,亮链接:
牛牛的单向链表_牛客题霸_牛客网https://www.nowcoder.com/practice/95559da7e19c4241b6fa52d997a008c4?tpId=291&tqId=2371704&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page牛牛的链表交换_牛客题霸_牛客网
https://www.nowcoder.com/practice/0e009fba6f3d47f0b5026b5f8b0cb1bc?tpId=291&tqId=2371720&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page牛牛的单链表求和_牛客题霸_牛客网
https://www.nowcoder.com/practice/a674715b6b3845aca0d5009bc27380b5?tpId=291&tqId=2372023&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page牛牛的双链表求和_牛客题霸_牛客网
https://www.nowcoder.com/practice/efb8a1fe3d1f439691e326326f8f8c95?tpId=291&tqId=2372041&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page牛牛的链表添加节点_牛客题霸_牛客网
https://www.nowcoder.com/practice/e33b79c2e15a41f9b541e73cd256124a?tpId=291&tqId=2372135&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page牛牛的链表删除_牛客题霸_牛客网
https://www.nowcoder.com/practice/d3df844baa8a4c139e103ca1b1faae0f?tpId=291&tqId=2372054&sourceUrl=%2Fexam%2Foj%3FquestionJobId%3D10%26subTabName%3Donline_coding_page接下来,就让我们开始我们的解题之旅吧,不过在这里我要先说明一下,以上这些题目都是基础题,而且大部分的知识点,我都有在前面几篇博客中讲到,所以这边是建议大家先去把前面几篇博客都吃透,我在本篇博客中并不会进行太细致入微的讲解,毕竟前几篇有所提及。
CC7 牛牛的单向链表:
这道题,简直可以说是最最基础的链表问题了,我们先看题目:
题意分析:
这道题的核心是 **“数组转单向链表 + 链表遍历输出”**,具体分析如下:
1. 题目核心需求
- 输入:先输入数组长度
n
,再输入n
个整数组成的数组。 - 处理:用这个数组构建一个单向链表(数组元素顺序与链表节点顺序一致)。
- 输出:顺序遍历这个链表,输出每个节点的值。
2. 关键步骤拆解
- 构建链表:需要定义链表节点结构(包含 “值
val
” 和 “指向下一节点的指针next
”),然后依次将数组元素转为节点,通过 “尾插法” 连接成链表(保证节点顺序与数组一致)。 - 遍历输出:从链表的头节点开始,逐个访问节点,输出节点值,直到遇到空节点(链表末尾)。
3. 示例理解(以示例 1 为例)
输入:n=4
,数组 [5,4,2,1]
。
- 构建链表:依次创建值为
5
、4
、2
、1
的节点,每个节点的next
指向下一个节点(形成5→4→2→1→null
的结构)。 - 遍历输出:从第一个节点(值为
5
)开始,依次输出5
、4
、2
、1
。
4. 考察点
这是一道链表基础操作的入门题,主要考察:
- 链表节点的定义与创建;
- 链表的 “尾插法” 构建(保持元素顺序);
- 链表的顺序遍历(访问每个节点并输出)。
适合用来练习链表最基本的 “创建 - 连接 - 遍历” 逻辑,是初学者熟悉链表操作的典型题目。
继续解答:
那么大家,这道题无非就是把之前的从1到n生成链表的题目换成数组罢了,我们本质上还是使用尾插法去创建新链表并不断插入新节点,不懂的可以去看一下这一篇博客:
我们这里就直接给出代码,详细注释版本的,帮助大家理解:
#include // 包含标准输入输出函数库,用于printf、scanf等
#include // 包含动态内存分配函数库,用于malloc、free等
#include // 包含断言函数库,用于assert宏进行调试检查
// 1. 定义数据类型别名
// 将int类型重命名为name1,方便后续统一修改节点存储的数据类型
// 例如若需存储float类型,只需修改为typedef float name1即可
typedef int name1;
// 2. 定义链表节点结构
// 结构体slist表示单向链表的一个节点
struct slist
{
name1 data; // 节点中存储的数据,类型为上面定义的name1(即int)
struct slist* next; // 指向链表中下一个节点的指针,类型为struct slist*
};
// 3. 简化结构体名称
// 为struct slist定义别名sl,后续使用sl即可代表struct slist,简化代码书写
typedef struct slist sl;
// 4. 链表打印函数
// 功能:从链表头节点开始,依次打印所有节点的数据
// 参数:phead - 链表的头指针(指向链表第一个节点)
void slprint(sl* phead)
{
// 断言检查:确保传入的头指针不为NULL
// 若phead为NULL,程序会终止并提示错误位置,用于调试阶段发现问题
assert(phead);
// 定义当前节点指针pucr(可理解为current的缩写),初始指向头节点
// 用这个指针遍历链表,避免直接修改头指针导致链表"丢失"
sl* pucr = phead;
// 遍历循环:当当前节点指针不为NULL时,继续循环(未到达链表末尾)
while (pucr != NULL)
{
// 打印当前节点存储的数据,格式为整数+空格
printf("%d ", pucr->data);
// 将当前节点指针向后移动一位,指向它的下一个节点
// 这一步是遍历的核心,通过指针移动访问链表的每个节点
pucr = pucr->next;
}
}
// 5. 新节点创建函数
// 功能:创建一个新的链表节点,并初始化其数据和指针
// 参数:x - 要存储在新节点中的数据(类型为name1)
// 返回值:新创建节点的指针(sl*类型)
sl* newslist(name1 x)
{
// 动态分配内存:申请一块大小为sl结构体(一个节点)的内存空间
// malloc返回void*类型,需要强制转换为sl*类型
sl* newsl = (sl*)malloc(sizeof(sl));
// 注意:实际开发中应检查内存分配是否成功(newsl是否为NULL)
// 此处简化处理,默认内存分配成功
// 初始化新节点的数据域:将参数x存入data成员
newsl->data = x;
// 初始化新节点的指针域:新节点暂时没有下一个节点,故next指向NULL
newsl->next = NULL;
// 返回新创建节点的指针,供调用者使用
return newsl;
}
// 主函数:程序入口
int main() {
// 6. 读取数组长度
int n; // 用于存储数组的长度
scanf("%d", &n); // 从标准输入读取n的值(用户输入数组长度)
// 7. 创建存储数据的数组
// 动态分配一个能存储n个int类型元素的数组
int* arr = (int*)malloc(n * sizeof(int));
// 8. 读取数组元素
// 循环n次,依次从输入读取数据存入数组arr中
for (int i = 0; i next = newslist(arr[i]);
// ② 将尾指针ptail向后移动,指向刚添加的新节点
// 保证ptail始终指向链表的最后一个节点,方便下次添加新节点
ptail = ptail->next;
}
// 10. 打印链表
// 调用slprint函数,传入头指针phead,打印整个链表的数据
slprint(phead);
// 11. 释放资源
// 释放之前动态分配的数组内存,避免内存泄漏
free(arr);
arr = NULL; // 好习惯:释放后将指针置空,避免野指针
// 注意:此处未释放链表节点的内存(简化处理)
// 实际开发中应遍历链表,逐个释放每个节点的内存
return 0; // 程序正常结束
}
至此,本题结束。
CC9 牛牛的单链表求和
其实,这道题也是很简单,我们先看题目:
题意分析:
这道题的核心是 **“数组转单链表 + 链表节点值求和”**,具体分析如下:
1. 题目核心需求
- 输入:首先输入数组长度
n
,接着输入n
个正整数组成的数组。 - 处理:将这个数组转换为单链表(数组元素顺序与链表节点顺序一致),然后遍历链表,对所有节点的值进行求和。
- 输出:输出链表所有节点值的总和。
2. 关键步骤拆解
- 数组转链表:定义链表节点结构(包含 “值
val
” 和 “指向下一节点的指针next
”),通过 “尾插法” 依次将数组元素转为节点并连接成链表(保证节点顺序与数组一致)。 - 遍历求和:从链表的头节点开始,逐个访问节点,将节点值累加,直到遇到空节点(链表末尾),最终得到总和并输出。
3. 示例理解(以示例 1 为例)
输入:n=5
,数组 [5,2,3,1,1]
。
- 数组转链表:依次创建值为
5
、2
、3
、1
、1
的节点,每个节点的next
指向下一个节点(形成5→2→3→1→1→null
的结构)。 - 遍历求和:从第一个节点(值为
5
)开始,依次累加节点值:5 + 2 + 3 + 1 + 1 = 12
,最终输出12
。
4. 考察点
这是一道链表基础操作 + 简单逻辑计算的题目,主要考察:
- 链表节点的定义、创建与连接(数组转链表的 “尾插法” 实现);
- 链表的顺序遍历(访问每个节点并累加值);
- 基本的求和逻辑与输出。
适合巩固 “链表创建 - 遍历” 的基础流程,同时加入简单的数值计算需求。
继续解答:
那么本题其实思路也很简单,就是我们先创建存储了数组数据的链表,然后再借助sum去对链表中每个节点存储的数据进行相加即可,这也是老生常谈的题目了,我们直接看详细注释版的代码即可,
#include // 引入标准输入输出库,提供printf、scanf等函数
#include // 引入标准库,提供malloc、free等动态内存管理函数
// 1. 定义数据类型别名
// 将int类型重命名为name1,这样如果后续需要修改节点存储的数据类型(如float),只需修改此处即可
typedef int name1;
// 2. 定义单链表节点结构
// 每个节点包含两部分:存储的数据和指向下一个节点的指针
struct slist
{
name1 data; // 节点中存储的具体数据,类型为上面定义的name1(即int)
struct slist* next; // 指针,用于指向链表中的下一个节点,类型为struct slist*
};
// 3. 简化结构体名称
// 为struct slist定义别名sl,后续代码中使用sl即可代表struct slist,减少代码冗余
typedef struct slist sl;
// 4. 创建新链表节点的函数
// 功能:在堆内存中创建一个新的链表节点,并初始化其数据和指针
// 参数:x - 要存入新节点的数据(类型为name1)
// 返回值:指向新创建节点的指针(sl*类型),若内存分配失败可能返回NULL(此处简化处理)
sl* newslist(name1 x)
{
// 调用malloc函数分配一块大小为sl结构体(一个节点)的内存空间
// 由于malloc返回void*类型,需要强制转换为sl*类型才能赋值给sl*类型的指针
sl* newsl = (sl*)malloc(sizeof(sl));
// 将传入的参数x存入新节点的数据域(data成员)
newsl->data = x;
// 新节点刚创建时没有后续节点,因此将指针域(next成员)初始化为NULL
newsl->next = NULL;
// 返回指向新节点的指针,供调用者使用
return newsl;
}
// 5. 计算链表所有节点数据之和并输出的函数
// 功能:从链表头节点开始遍历所有节点,累加每个节点的数据,最后输出总和
// 参数:phead - 链表的头指针(指向链表的第一个节点)
void slistadd(sl* phead)
{
// 定义sum变量用于存储累加的总和,初始值为0
name1 sum = 0;
// 遍历链表的循环:当phead不为NULL时(即还未到达链表末尾),继续循环
while (phead != NULL)
{
// 将当前节点的数据(phead->data)累加到sum中
sum = sum + phead->data;
// 将phead指针向后移动一位,指向当前节点的下一个节点
// 这是遍历链表的核心操作,通过指针移动访问下一个节点
phead = phead->next;
}
// 遍历结束后,sum中存储了所有节点数据的总和,通过printf输出
printf("%d", sum);
}
// 主函数:程序的入口点,负责接收输入、构建链表、调用求和函数等核心流程
int main() {
// 6. 接收用户输入的数组长度
int n; // 声明变量n,用于存储数组的长度
scanf("%d", &n); // 通过scanf函数从标准输入(键盘)读取n的值,&n表示取n的地址
// 7. 动态分配数组内存
// 调用malloc函数分配一块能存储n个int类型元素的内存空间,将返回的指针强制转换为int*类型
int* arr = (int*)malloc(n * sizeof(int));
// 8. 接收用户输入的数组元素
// 循环n次,依次读取n个整数存入数组arr中
for (int i = 0; i next = newslist(arr[i]);
// ② 将尾指针ptail向后移动,指向刚添加的新节点
// 保证ptail始终指向链表的最后一个节点,以便下一次添加新节点时直接使用
ptail = ptail->next;
}
// 10. 调用求和函数计算并输出链表所有节点数据的总和
// 将头指针phead传给slistadd函数,该函数会从表头开始遍历并求和
slistadd(phead);
// 11. 释放动态分配的资源,避免内存泄漏
free(arr); // 释放之前为数组分配的内存
arr = NULL; // 将指针置空,避免成为野指针(指向已释放内存的指针)
// 注意:此处未释放链表节点的内存(题目简化处理)
// 实际开发中,需要遍历链表并逐个释放每个节点的内存,避免内存泄漏
return 0; // 主函数返回0,表示程序正常结束
}
又一题结束。
CC10 牛牛的双链表求和
这道题其实和上一题类似,只是多了一个链表,让我们对两个链表的数据进行相加罢了,我们看题目:
题意分析:
这道题的核心是 **“双数组转双链表 + 对应位置节点值相加 + 输出结果链表”**,具体分析如下:
1. 题目核心需求
- 输入:
- 第一行输入正整数
n
,表示两个数组的长度(两个数组长度相同)。 - 第二行输入
n
个正整数,作为数组a
的元素。 - 第三行输入
n
个正整数,作为数组b
的元素。
- 第一行输入正整数
- 处理:
- 将数组
a
转换为链表a
,数组b
转换为链表b
(数组元素顺序与链表节点顺序一致)。 - 按顺序将链表
a
中每个节点的值,加到链表b
对应位置节点的值上(即链表a
的第i
个节点值,加到链表b
的第i
个节点值上)。
- 将数组
- 输出:输出相加后的链表
b
的所有节点值。
2. 关键步骤拆解
- 双数组转双链表:分别为数组
a
和数组b
定义链表结构,通过 “尾插法” 依次将数组元素转为节点并连接成两个链表(保证节点顺序与数组一致)。 - 对应节点值相加:同时遍历两个链表,将链表
a
节点的值累加到链表b
对应位置节点的值上(需保证两个链表同步遍历,因为长度相同)。 - 输出结果链表:遍历处理后的链表
b
,输出每个节点的值。
3. 示例理解(以示例 1 为例)
输入:
n=5
- 数组
a
:[5,4,2,1,3]
- 数组
b
:[2,4,5,8,9]
处理过程:
- 数组转链表:链表
a
为5→4→2→1→3→null
;链表b
为2→4→5→8→9→null
。 - 对应节点相加:
- 第 1 个节点:
5 + 2 = 7
- 第 2 个节点:
4 + 4 = 8
- 第 3 个节点:
2 + 5 = 7
- 第 4 个节点:
1 + 8 = 9
- 第 5 个节点:
3 + 9 = 12
- 第 1 个节点:
- 输出链表
b
:7→8→7→9→12→null
,最终输出7 8 7 9 12
。
4. 考察点
这是一道双链表操作 + 同步遍历与数值计算的题目,主要考察:
- 双链表的定义、创建与连接(两个数组分别转链表的 “尾插法” 实现);
- 双链表的同步遍历(保证两个链表同时访问对应位置节点);
- 节点值的相加逻辑与结果链表的输出。
需要注意链表长度一致的前提,以及同步遍历时指针的协调操作
继续解答:
所以,经过上述分析我们就知道,本题也是非常简单,只不过我们要注意的是把哪一个链表的数据加到哪一个链表的数据中,我们依旧是直接上代码:
#include // 引入标准输入输出库,提供printf、scanf等输入输出函数
#include // 引入标准库,提供malloc、free等动态内存分配与释放函数
// 1. 定义数据类型别名
// 将int类型重命名为name1,这样如果后续后续需要修改节点存储的数据类型(如float),只需修改此处即可
typedef int name1;
// 2. 定义单链表节点结构并简化名称
// 使用typedef将结构体struct slist直接重命名为sl,简化后续代码中的类型声明
typedef struct slist
{
name1 data; // 节点的数据域,用于存储具体数值,类型为name1(即int)
struct slist* next; // 节点的指针域,用于指向链表中的下一个节点,类型为struct slist*
}sl;
// 3. 创建新链表节点的函数
// 功能:在堆内存中申请一块空间用于存储新节点,并初始化节点的数据和指针
// 参数:x - 要存储在新节点中的数据(类型为name1)
// 返回值:指向新创建节点的指针(sl*类型),若内存分配失败可能返回NULL(此处简化处理)
sl* createsl(name1 x)
{
// 调用malloc函数分配一块大小为sl结构体(一个节点)的内存空间
// malloc返回void*类型,需要强制转换为sl*类型才能赋值给sl*类型的指针变量
sl* newsl = (sl*)malloc(sizeof(sl));
// 将传入的参数x赋值给新节点的数据域,完成数据初始化
newsl->data = x;
// 新节点刚创建时没有后续节点,因此将指针域初始化为NULL
newsl->next = NULL;
// 返回指向新节点的指针,供调用者使用
return newsl;
}
// 4. 打印链表所有节点数据的函数
// 功能:从链表的头节点开始,依次遍历每个节点并打印其数据
// 参数:phead - 链表的头指针(指向链表的第一个节点)
void slprint(sl* phead)
{
// 定义临时指针temp并初始化为头指针phead
// 使用临时指针遍历可以避免修改原头指针,防止链表"丢失"
sl* temp = phead;
// 遍历循环:当temp不为NULL时(即当前节点存在,未到达链表末尾),继续循环
// while(temp) 等价于 while(temp != NULL),是一种简洁的写法
while (temp)
{
// 打印当前节点的数据域的值,格式为"整数 + 空格"
printf("%d ", temp->data);
// 将临时指针temp向后移动一位,指向当前节点的下一个节点
// 这是遍历链表的核心操作,通过指针移动实现对每个节点的访问
temp = temp->next;
}
}
// 5. 两个链表对应节点值相加的函数
// 功能:同步遍历链表a和链表b,将链表a中每个节点的值累加到链表b对应位置的节点上
// 参数:pheada - 链表a的头指针;pheadb - 链表b的头指针
// 说明:题目保证两个链表长度相同,因此无需处理长度不一致的情况
void sladd(sl* pheada, sl* pheadb)
{
// 同步遍历两个链表:当两个指针都不为NULL时(即两个链表都未遍历结束),继续循环
while (pheada != NULL && pheadb != NULL)
{
// 将链表a当前节点的数据(pheada->data)累加到链表b当前节点的数据(pheadb->data)上
pheadb->data += pheada->data;
// 将链表a的指针向后移动一位,指向它的下一个节点
pheada = pheada->next;
// 将链表b的指针向后移动一位,指向它的下一个节点
// 两个指针同步移动,保证访问的是对应位置的节点
pheadb = pheadb->next;
}
}
// 主函数:程序的入口点,负责接收输入数据、创建链表、调用核心功能函数
int main() {
// 6. 读取两个数组的长度(题目说明两个数组长度相同)
int n; // 声明变量n,用于存储数组的长度
scanf("%d", &n); // 通过scanf函数从标准输入(键盘)读取n的值,&n表示取n的地址
// 7. 为第一个数组(数组a)分配内存并读取数据
// 调用malloc函数动态分配一块能存储n个int类型元素的内存空间
// 将返回的指针强制转换为int*类型后赋值给指针变量a
int* a = (int*)malloc(n * sizeof(int));
// 循环n次,依次从输入读取n个整数并存入数组a中
for (int i = 0; i next = createsl(a[i]);
// ② 将尾指针ptaila向后移动,指向刚添加的新节点
// 保证ptaila始终指向链表a的最后一个节点,方便下次添加新节点
ptaila = ptaila->next;
}
// 10. 根据数组b创建链表b(与创建链表a的逻辑完全相同)
// 10.1 创建链表b的头节点:使用数组b的第一个元素(b[0])
sl* pheadb = createsl(b[0]);
// 定义链表b的尾指针ptailb,初始时指向头节点
sl* ptailb = pheadb;
// 10.2 循环创建链表b的剩余节点(从数组b的第二个元素开始)
for (int i = 1; i next = createsl(b[i]);
// ② 将尾指针ptailb向后移动到新节点
ptailb = ptailb->next;
}
// 11. 调用sladd函数,将链表a的节点值累加到链表b对应节点上
sladd(pheada, pheadb);
// 12. 调用slprint函数,打印相加后的链表b的所有节点数据
slprint(pheadb);
// 13. 释放动态分配的内存资源,避免内存泄漏
free(a); // 释放为数组a分配的内存
free(b); // 释放为数组b分配的内存
a = NULL; // 将指针a置空,避免成为野指针(指向已释放内存的指针)
b = NULL; // 将指针b置空,同样避免野指针
// 注意:此处未释放两个链表中节点的内存(题目简化处理)
// 在实际开发中,需要遍历两个链表,逐个释放每个节点的内存,否则会造成内存泄漏
return 0; // 主函数返回0,表示程序正常结束
}
大功告成。
CC11 牛牛的链表删除
本题和之前所讲的leetcode中的题目一样,大家可以直接去看这一篇博客:对于单链表相关经典算法题:203. 移除链表元素的解析-CSDN博客
在这里我就只给出详细代码:
#include // 引入标准输入输出库,提供printf、scanf等函数
#include // 引入标准库,提供malloc、free等内存管理函数
#include // 引入断言库,用于调试时的参数校验
// 定义数据类型别名,方便后续修改节点存储的数据类型
typedef int name1;
// 定义单链表节点结构
typedef struct slist
{
name1 data; // 节点存储的数据
struct slist* next; // 指向链表下一个节点的指针
} sl;
/**
* 创建新的链表节点
* @param x 要存储在新节点中的数据
* @return 指向新创建节点的指针,内存分配失败时返回NULL
*/
sl* createsl(name1 x)
{
// 为新节点分配内存空间
sl* newsl = (sl*)malloc(sizeof(sl));
// 检查内存分配是否成功
if (newsl == NULL)
{
printf("内存分配失败\n");
return NULL;
}
// 初始化节点数据
newsl->data = x;
// 新节点初始没有后续节点,指针置空
newsl->next = NULL;
return newsl;
}
/**
* 打印链表中所有节点的数据
* @param phead 链表的头指针
*/
void slprint(sl* phead)
{
// 遍历链表,当前节点不为空时继续
while (phead != NULL)
{
// 打印当前节点的数据
printf("%d ", phead->data);
// 移动到下一个节点
phead = phead->next;
}
}
/**
* 删除链表中指定位置的节点
* @param phead 链表的头指针
* @param position 要删除的节点指针
* @note 该函数假设position是链表中的有效节点,且phead不为空
*/
void slbreak(sl* phead, sl* position)
{
// 断言确保传入的指针有效
assert(phead != NULL && position != NULL);
// 找到要删除节点的前一个节点
sl* temp = phead;
while (temp->next != position)
{
temp = temp->next;
// 防止position不在链表中导致无限循环
assert(temp != NULL);
}
// 保存要删除节点的下一个节点地址
sl* temp1 = position->next;
// 将前一个节点的指针指向要删除节点的下一个节点,完成删除
temp->next = temp1;
// 释放被删除节点的内存,避免内存泄漏
free(position);
position = NULL; // 避免野指针
}
/**
* 从链表中删除所有值为x的节点
* @param phead 链表的头指针的指针(使用二级指针以处理头节点被删除的情况)
* @param x 要删除的节点值
*/
void slfinderase(sl** phead, name1 x)
{
// 断言确保头指针的指针有效
assert(phead != NULL);
// 处理头节点为空的情况
if (*phead == NULL)
return;
// 临时指针用于遍历链表
sl* position = *phead;
// 前驱指针,用于处理头节点被删除的情况
sl* prev = NULL;
while (position != NULL)
{
// 找到值为x的节点
if (position->data == x)
{
// 保存当前节点的下一个节点地址
sl* nextNode = position->next;
// 特殊处理头节点
if (position == *phead)
{
// 更新头指针
*phead = nextNode;
// 释放原头节点内存
free(position);
}
else
{
// 非头节点,通过前驱节点删除
prev->next = nextNode;
free(position);
}
// 移动到下一个节点
position = nextNode;
}
else
{
// 不是要删除的节点,更新前驱指针并移动到下一个节点
prev = position;
position = position->next;
}
}
}
/**
* 释放整个链表占用的内存
* @param phead 链表的头指针的指针
*/
void freeList(sl** phead)
{
if (phead == NULL || *phead == NULL)
return;
sl* current = *phead;
sl* next = NULL;
// 逐个释放节点
while (current != NULL)
{
next = current->next;
free(current);
current = next;
}
// 头指针置空,避免野指针
*phead = NULL;
}
int main() {
int n, x;
// 读取数组长度n和要删除的值x
scanf("%d%d", &n, &x);
// 为数组分配内存
int* arr = (int*)malloc(n * sizeof(int));
if (arr == NULL)
{
printf("内存分配失败\n");
return 1; // 异常退出
}
// 读取数组元素
for (int i = 0; i next = newNode;
ptail = ptail->next;
}
// 从链表中删除所有值为x的节点
// 使用二级指针传递头指针,以处理头节点被删除的情况
slfinderase(&phead, x);
// 打印处理后的链表
slprint(phead);
// 释放资源
free(arr); // 释放数组内存
arr = NULL;
freeList(&phead); // 释放链表内存
return 0; // 程序正常结束
}
至此,又一题被我们解决了。
CC12 牛牛的链表添加节点
这一题,才算是加上了一丢丢难度,但是也不难,我们看题目:
题意分析:
这道题的核心是 **“数组转链表 + 在指定位置后插入新节点”**,具体分析如下:
1. 题目核心需求
- 输入:
- 第一行输入两个正整数
n
和i
,n
是数组长度,i
是要添加新节点的位置(在第i
个节点后插入)以及新节点的值(值为i
)。 - 第二行输入
n
个正整数,作为数组的元素。
- 第一行输入两个正整数
- 处理:
- 将数组转换为单链表(数组元素顺序与链表节点顺序一致)。
- 在链表的第
i
个节点后面,插入一个值为i
的新节点(节点计数从 1 开始)。
- 输出:输出插入新节点后的完整链表。
2. 关键步骤拆解
- 数组转链表:定义链表节点结构(包含 “值
val
” 和 “指向下一节点的指针next
”),通过 “尾插法” 依次将数组元素转为节点并连接成链表(保证节点顺序与数组一致)。 - 定位插入位置:遍历链表,找到第
i
个节点。 - 插入新节点:在第
i
个节点后,插入值为i
的新节点(需处理 “找到节点后,修改指针连接新节点” 的逻辑)。
3. 示例理解(以示例 1 为例)
输入:
n=5
,i=3
- 数组:
[5,4,8,6,3]
处理过程:
- 数组转链表:链表为
5→4→8→6→3→null
。 - 定位第
3
个节点:值为8
的节点。 - 插入新节点:在值为
8
的节点后,插入值为3
的新节点,链表变为5→4→8→3→6→3→null
。 - 输出链表:
5 4 8 3 6 3
。
4. 考察点
这是一道链表插入操作的题目,主要考察:
- 链表节点的定义、创建与连接(数组转链表的 “尾插法” 实现);
- 链表的遍历(定位到第
i
个节点); - 链表的插入操作(修改指针,在指定节点后插入新节点,需注意指针的修改顺序,避免断链)。
继续解答:
看完上面的分析之后,大家应该已经对这道题知根知底了,但是为什么我会说这道题稍微有一点难度呢,那是因为在于我们要去正确的找到第i个节点,并且顺利的在链表的第i个节点后插入存储i的新节点,那么细节也就出现在这里了,我们要怎么去找到第i个节点呢?
其实这个问题与我们之前在对于单链表相关经典算法题:206. 反转链表及876. 链表的中间结点的解析-CSDN博客这一篇博客中讲的寻找中间节点的题目很像,大家可以去看看里面half的移动,其与本题一样,我这里也就不赘述,我们直接看寻找第i个节点的代码:
sl* slfind(sl* phead,name1 i)
{
i = i-1;//点睛之笔,才能找到第i个节点
sl* position=phead;
while(i)
{
position=position->next;
i--;
}
//出来之后的position就是第i个节点
return position;
}
下面是详细注释版本:
/**
* 查找链表中指定位置的节点(节点位置从1开始计数)
* @param phead 链表的头指针,指向链表的第一个节点
* @param i 要查找的节点位置(正整数,从1开始)
* @return 指向第i个节点的指针;若i无效(如大于链表长度),可能返回野指针(需提前调用前确保i有效)
*/
sl* slfind(sl* phead, name1 i)
{
// 核心转换:将"从1开始的位置"转为"从0开始的偏移量"
// 例如:要找第1个节点 → 偏移量为0(无需移动指针)
// 要找第2个节点 → 偏移量为1(移动1次指针)
// 要找第5个节点 → 偏移量为4(移动4次指针)
i = i - 1;
// 定义位置指针position,初始指向链表的第一个节点(第1个节点)
// 此时position指针指向的是潜在的"第1个节点"
sl* position = phead;
// 循环移动指针,次数为转换后的i值(偏移量)
// 当i为0时,循环不执行,直接返回初始节点(第1个节点)
while (i) // 等价于 while(i != 0)
{
// 将position指针指针向后指针向后移动一位,指向当前节点的下一个节点
// 每执行一次,指针向链表尾部移动一个节点
position = position->next;
// 偏移量减1,控制循环执行次数
i--;
}
// 循环结束后,position指针恰好指向目标节点
// 例如:初始i=3(找第3个节点)→ 转换后i=2 → 循环2次 → 指针移动2次 → 指向第3个节点
return position;
}
大家看了之后就能理解了,解决了这一个小细节之后,本题也就毫无难度可言了,我们看本题的完整详细注释版代码:
#include // 引入标准输入输出库,提供printf、scanf等函数
#include // 引入标准库,提供malloc等内存分配函数
// 1. 定义数据类型别名
// 将int类型重命名为name1,方便后续统一修改节点存储的数据类型
typedef int name1;
// 2. 定义单链表节点结构
// 每个节点包含数据域和指向下一节点的指针域
typedef struct slist
{
name1 data; // 节点存储的数据,类型为name1(即int)
struct slist* next; // 指向链表中下一个节点的指针
} sl;
/**
* 创建新的链表节点
* @param x 要存储在新节点中的数据
* @return 指向新创建节点的指针,内存分配失败时可能返回NULL
*/
sl* createsl(name1 x)
{
// 动态分配一个节点大小的内存空间
// malloc返回void*类型,此处处省略强制转换(C语言允许隐式转换)
sl* newsl = malloc(sizeof(sl));
// 初始化节点的数据域为x
newsl->data = x;
// 新节点初始没有后续节点,指针域置为NULL
newsl->next = NULL;
// 返回新节点的指针
return newsl;
}
/**
* 打印链表中所有节点的数据
* @param phead 链表的头指针(指向第一个节点)
*/
void slprint(sl* phead)
{
// 定义临时指针temp指向头节点,避免修改原头指针
sl* temp = phead;
// 遍历链表:当temp不为NULL时(未到达链表末尾)
while (temp) // 等价于 while(temp != NULL)
{
// 打印当前节点的数据,后跟空格
printf("%d ", temp->data);
// 移动临时指针到下一个节点
temp = temp->next;
}
}
/**
* 查找链表中第i个节点(节点位置从1开始计数)
* @param phead 链表的头指针
* @param i 要查找的节点位置(正整数)
* @return 指向第i个节点的指针(假设i是有效的,即不超过链表长度)
*/
sl* slfind(sl* phead, name1 i)
{
// 关键转换:将"从1开始的位置"转为"从0开始的偏移量"
// 例如:查找第1个节点 → i变为0(无需移动指针)
// 查找第3个节点 → i变为2(需要移动2次指针)
i = i - 1;
// 定义位置指针position,初始指向头节点(第1个节点)
sl* position = phead;
// 循环移动指针i次,定位到目标节点
while (i) // 当i>0时继续循环
{
// 指针向后移动一位,指向当前节点的下一个节点
position = position->next;
// 偏移量减1,控制循环次数
i--;
}
// 循环结束后,position指向第i+1个节点(即原需求的第i个节点)
return position;
}
/**
* 在链表的指定位置后插入新节点
* @param phead 链表的头指针(此处未使用,保留参数可用于扩展)
* @param position 要插入位置的前一个节点(在该节点后插入新节点)
* @param i 新节点要存储的数据(题目要求值为idx)
*/
void sladd(sl* phead, sl* position, name1 i)
{
// 创建新节点,数据为i
sl* newsl = createsl(i);
// 步骤1:新节点的next指针指向position节点的下一个节点
// 保存position原本的后续节点,避免链表断裂
newsl->next = position->next;
// 步骤2:position节点的next指针指向新节点
// 完成新节点的插入
position->next = newsl;
}
int main() {
// 3. 读取输入数据
int n, idx;
// 读取数组长度n和要插入的位置idx(在第idx个节点后插入)
scanf("%d%d", &n, &idx);
// 4. 读取数组元素
// 动态分配存储n个整数的数组
int* arr = (int*)malloc(n * sizeof(int));
// 循环读取n个整数存入数组
for (int i = 0; i next = createsl(arr[i]);
// 尾指针后移到新创建的节点,保持始终指向链表末尾
ptail = ptail->next;
}
// 6. 执行插入操作
// 6.1 查找第idx个节点的位置
sl* position = slfind(phead, idx);
// 6.2 在该节点后插入新节点,新节点的数据为idx
sladd(phead, position, idx);
// 7. 打印插入后的链表
slprint(phead);
// 8. 释放资源
free(arr); // 释放数组占用的内存
arr = NULL; // 指针置空,避免野指针
// 注意:实际开发中需遍历链表释放所有节点的内存(题目简化处理)
return 0; // 程序正常结束
}
CC8 牛牛的链表交换
个人觉得本题算是这几题里面唯一有点难度的题目,我们先看题目:
题意分析:
这道题的核心是 **“数组转链表 + 交换链表首尾特定节点”**,具体分析如下:
1. 题目核心需求
- 输入:
- 第一行输入正整数
n
,表示数组的长度。 - 第二行输入
n
个正整数,作为数组的元素。
- 第一行输入正整数
- 处理:
- 将数组转换为单链表(数组元素顺序与链表节点顺序一致)。
- 交换链表的前两个节点的位置。
- 交换链表的最后两个节点的位置。
- 输出:输出交换节点后的完整链表。
2. 关键步骤拆解
- 数组转链表:定义链表节点结构(包含 “值
val
” 和 “指向下一节点的指针next
”),通过 “尾插法” 依次将数组元素转为节点并连接成链表(保证节点顺序与数组一致)。 - 交换前两个节点:需处理 “头节点变更” 的情况(若原链表头节点是
node1
,交换后node2
成为新头节点,node1
接在node2
之后)。 - 交换最后两个节点:需先遍历找到倒数第二个节点和最后一个节点,再修改它们的指针(让倒数第二个节点的
next
指向null
,最后一个节点的next
指向倒数第二个节点,并让倒数第三个节点的next
指向最后一个节点)。
3. 示例理解
- 示例 1:
输入:n=4
,数组[2,3,4,5]
。
数组转链表:2→3→4→5→null
。
交换前两个节点:3→2→4→5→null
。
交换最后两个节点:3→2→5→4→null
,输出3 2 5 4
。 - 示例 2:
输入:n=3
,数组[3,2,1]
。
数组转链表:3→2→1→null
。
交换前两个节点:2→3→1→null
。
交换最后两个节点:2→1→3→null
,输出2 1 3
。
4. 考察点
这是一道链表节点交换的题目,主要考察:
- 链表节点的定义、创建与连接(数组转链表的 “尾插法” 实现);
- 链表节点交换的指针操作(前两个节点交换需处理头节点变更,最后两个节点交换需定位倒数节点并修改指针);
- 边界情况处理(如链表长度为 2 时,前两个和最后两个节点是同一组,交换一次即可;链表长度小于 2 时,无需交换)。
继续解答:
经过上面的分析,我们知道,本题是要求把一个链表的第一二个节点互相交换,然后再去把倒一倒二个节点进行互换,大家要注意先后顺序哦。
那么知道了之后,其实也就不难了这一道题目,我们的思路就是:
使用二级指针(即传址调用),去把第二个节点赋值给第一个节点,要记得节点中next指针的变换哦,然后进行了这一步之后,我们再去把链表的倒一倒二个节点进行互换,这里便不需要传址调用,我们只需注意节点中next指针的改变就行。
但是在倒一倒二的节点的寻找中,我们肯定是要借助循环去遍历的,同时我们也要找到倒三个节点,去对其next指针的改变,那么我们要怎么去找到倒三个节点呢?具体如下:
// 定位指针用于定位链表的最后三个节点
sl* p1 = *pphead; // p1 最终指向最后一个节点
sl* p2 = *pphead; // p2 最终指向倒数第二个节点
sl* p3 = NULL; // p3 最终指向倒数第三个节点(若存在)
// 遍历链表,定位最后三个节点
// 循环条件:当p1的next不为NULL时,说明p1不是最后一个节点
while (p1->next != NULL)
{
p3 = p2; // p3 移动到当前p2的位置(为p2后移做准备)
p2 = p1; // p2 移动到当前p1的位置
p1 = p1->next; // p1 向后移动一个节点
}
以下是其原理:
这段代码的核心逻辑是通过一次遍历,准确定链表中最后三个节点的位置,为后续交换最后两个节点做准备。下面是详细的逻辑拆解:
核心目标
找到链表的:
- 最后一个节点(
p1
) - 倒数第二个节点(
p2
) - 倒数第三个节点(
p3
,若存在)
初始状态
sl* p1 = *pphead; // p1 初始指向头节点(第一个节点)
sl* p2 = *pphead; // p2 初始指向头节点(第一个节点)
sl* p3 = NULL; // p3 初始为 NULL(暂时无指向)
pphead
是链表头指针的地址(二级指针),*pphead
就是链表的头节点。- 三个指针的初始状态:
p1
和p2
都指向头节点,p3
为空(因为还没有遍历到第三个节点)。
遍历过程(核心逻辑)
while (p1->next != NULL) // 循环条件:p1 不是最后一个节点
{
p3 = p2; // 步骤1:p3 移动到当前 p2 的位置
p2 = p1; // 步骤2:p2 移动到当前 p1 的位置
p1 = p1->next; // 步骤3:p1 向后移动一个节点(探索下一个节点)
}
遍历步骤拆解
以链表 A→B→C→D→E→NULL
(共 5 个节点)为例,跟踪指针移动过程:
初始状态(循环前):
p1
指向A
(头节点)p2
指向A
p3
为NULL
第 1 次循环(
p1->next
是B
,非空):p3 = p2
→p3
指向A
p2 = p1
→p2
指向A
p1 = p1->next
→p1
指向B
- 结果:
p1=B, p2=A, p3=A
第 2 次循环(
p1->next
是C
,非空):p3 = p2
→p3
指向A
p2 = p1
→p2
指向B
p1 = p1->next
→p1
指向C
- 结果:
p1=C, p2=B, p3=A
第 3 次循环(
p1->next
是D
,非空):p3 = p2
→p3
指向B
p2 = p1
→p2
指向C
p1 = p1->next
→p1
指向D
- 结果:
p1=D, p2=C, p3=B
第 4 次循环(
p1->next
是E
,非空):p3 = p2
→p3
指向C
p2 = p1
→p2
指向D
p1 = p1->next
→p1
指向E
- 结果:
p1=E, p2=D, p3=C
循环结束(
p1->next
是NULL
,p1
到达最后一个节点):- 最终指针位置:
p1
指向E
(最后一个节点)p2
指向D
(倒数第二个节点)p3
指向C
(倒数第三个节点)
- 最终指针位置:
最终结果
循环结束后:
p1
一定指向最后一个节点(因为循环在p1->next == NULL
时停止)。p2
指向倒数第二个节点(始终比p1
慢一步)。p3
指向倒数第三个节点(始终比p2
慢一步,若链表长度<3 则仍为NULL
)。
作用
通过这种 “三指针接力” 的遍历方式,只需一次遍历就能准确定位链表末尾的三个节点,为后续 “交换最后两个节点” 提供指针基础:
- 交换
p1
(最后一个节点)和p2
(倒数第二个节点)的位置时,需要p3
(倒数第三个节点)的指针来修改链表连接(p3->next
需指向新的倒数第二个节点)。
如此大家应该就能明白了。
那么到了这里,本题也就没有什么难度了,我们直接上完整注释版的详细代码:
#include // 引入标准输入输出库,提供printf、scanf等函数
#include // 引入标准库,提供malloc、free等内存管理函数
#include // 引入断言库,用于调试时的参数有效性检查
// 1. 定义数据类型别名
// 将int类型重命名为name1,方便后续统一修改节点存储的数据类型(如改为float)
typedef int name1;
// 2. 定义单链表节点结构
struct slist
{
name1 data; // 节点存储的数据,类型为name1(即int)
struct slist* next; // 指向链表中下一个节点的指针
};
// 为结构体struct slist定义别名sl,简化代码书写
typedef struct slist sl;
/**
* 创建新的链表节点
* @param x 要存储在新节点中的数据
* @return 指向新节点的指针,内存分配失败时返回NULL
*/
sl* newslist(name1 x)
{
// 动态分配一个节点大小的内存空间
sl* newsl = (sl*)malloc(sizeof(sl));
// 检查内存分配是否成功(健壮性处理)
if (newsl == NULL)
{
printf("内存分配失败,无法创建新节点\n");
return NULL;
}
// 初始化节点的数据域
newsl->data = x;
// 新节点初始无后续节点,指针域置为NULL
newsl->next = NULL;
return newsl;
}
/**
* 交换链表的前两个节点
* @param pphead 链表头指针的地址(二级指针)
* @note 仅当链表至少包含两个节点时有效
*/
void slswapfront(sl** pphead)
{
// 断言检查:确保二级指针有效、头节点存在、且至少有两个节点
// 避免空指针访问或对短链表执行无效操作
assert(pphead != NULL && *pphead != NULL && (*pphead)->next != NULL);
// 保存第一个节点的地址(原头节点)
sl* temp1 = *pphead;
// 保存第二个节点的地址
sl* temp2 = (*pphead)->next;
// 保存第三个节点的地址(可能为NULL,若链表只有两个节点)
sl* temp3 = temp2->next;
// 步骤1:第二个节点的next指针指向第一个节点
// 此时链表关系:temp2 → temp1
temp2->next = temp1;
// 步骤2:更新头指针(通过二级指针修改外部头节点)
// 让头指针指向第二个节点,此时temp2成为新的头节点
*pphead = temp2;
// 步骤3:第一个节点的next指针指向原来的第三个节点
// 完成交换后:temp2 → temp1 → temp3(原第三个节点及后续)
temp1->next = temp3;
}
/**
* 交换链表的最后两个节点
* @param pphead 链表头指针的地址(二级指针)
* @note 仅当链表至少包含两个节点时有效
*/
void slswapback(sl** pphead)
{
// 断言检查:确保二级指针有效、头节点存在、且至少有两个节点
assert(pphead != NULL && *pphead != NULL && (*pphead)->next != NULL);
// 初始化三个指针,用于定位链表尾部节点
sl* p1 = *pphead; // 最终指向最后一个节点
sl* p2 = *pphead; // 最终指向倒数第二个节点
sl* p3 = NULL; // 最终指向倒数第三个节点(若存在)
// 遍历链表,定位最后三个节点
// 循环条件:p1的next不为NULL(即p1不是最后一个节点)
while (p1->next != NULL)
{
p3 = p2; // p3跟进到p2当前位置
p2 = p1; // p2跟进到p1当前位置
p1 = p1->next; // p1向前移动一个节点
}
// 循环结束后:
// p1 = 最后一个节点,p2 = 倒数第二个节点,p3 = 倒数第三个节点
// 步骤1:倒数第三个节点的next指向最后一个节点
p3->next = p1;
// 步骤2:最后一个节点的next指向原来的倒数第二个节点
p1->next = p2;
// 步骤3:原来的倒数第二个节点的next置为NULL(成为新的尾节点)
p2->next = NULL;
}
/**
* 打印链表所有节点的数据
* @param phead 链表的头指针
*/
void slprint(sl* phead)
{
// 定义遍历指针pucr(可理解为"current"的缩写),初始指向头节点
sl* pucr = phead;
// 遍历链表:当pucr不为NULL时(未到达链表末尾)
while (pucr != NULL)
{
// 打印当前节点的数据,后跟空格
printf("%d ", pucr->data);
// 移动指针到下一个节点
pucr = pucr->next;
}
}
int main() {
// 3. 读取输入数据
int n; // 存储数组长度
scanf("%d", &n); // 从输入读取数组长度n
// 4. 读取数组元素
// 动态分配存储n个int元素的数组
int* arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) // 检查数组内存分配是否成功
{
printf("内存分配失败,无法创建数组\n");
return 1; // 异常退出程序
}
// 循环读取n个整数存入数组
for (int i = 0; i next = newNode; // 将新节点连接到链表尾部
ptail = ptail->next; // 尾指针后移到新节点
}
// 6. 准备二级指针(头指针的地址)
// pphead是指向头指针phead的指针,用于在函数中修改phead的值
sl** pphead = &phead;
// 7. 执行交换操作(仅当链表长度≥2时)
if (n >= 2)
{
slswapfront(pphead); // 交换前两个节点
slswapback(pphead); // 交换最后两个节点
}
// 8. 打印交换后的链表
slprint(*pphead); // *pphead即当前的头指针
// 9. 释放资源
free(arr); // 释放数组内存
arr = NULL; // 指针置空,避免野指针
// 注意:实际开发中需遍历链表,释放所有节点的内存(此处简化处理)
return 0; // 程序正常结束
}
到此,本题也是大功告成。
结语:
到这里,牛客上这几道链表基础题就都解析完了。从最简单的数组转链表、遍历输出,到稍复杂的节点插入、删除,再到需要仔细处理指针关系的节点交换,每一道题都像一块拼图,拼出了链表操作的核心脉络 —— 指针的移动、节点的连接、边界的考量。
其实回过头看,这些题目本身并没有太多高深的技巧,更多的是对 “基本功” 的打磨。就像练武功要先扎马步,学链表也得把这些基础操作练到熟练于心:尾插法建链表时,尾指针的步步跟进;查找节点时,偏移量的巧妙转换;交换节点时,指针指向的小心调整…… 这些细节看似琐碎,却是日后解决更复杂链表问题的底气。
可能有小伙伴会觉得,反复练这些基础题有点枯燥,但正是这种 “枯燥” 的积累,才能让我们在面对更难的题目时,一眼看穿本质,快速找到思路。就像盖房子,地基打得越牢,往上添砖加瓦时才越稳。
随着这篇内容的结束,我们的链表基础题解析也暂时告一段落了。但这绝不是结束,而是新的开始 —— 接下来,我们会朝着更有挑战的内容进发,把这些基础能力运用到更复杂的场景中去。
感谢大家一路的陪伴和坚持,希望这些解析能帮你在链表的学习路上走得更扎实。咱们很快会在新的内容里再见,一起去攻克更多难关,解锁更多技能~
相关文章:
详细介绍:对于牛客网—语言学习篇—C语言入门—链表的题目解析
详细介绍:对于牛客网—语言学习篇—C语言入门—链表的题目解析pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New&quo…...
Day17Arrays类的初步认识
package com.cc.array;import java.util.Arrays;public class ArrayDem6 {public static void main(String[] args) {int[] a = {12, 3, 43, 4, 235, 5, 6, 45, 7, 7};System.out.println(a);//[I@f6f4d33//打印数组元素System.out.println(Arrays.toString(a));//toString:以字…...
小学生模拟赛题解
A 正常做这题显然 \(10^{18}\) 是不可做的,所以问题一定出现在 gen 上。 注意到 \(7\mid2009\),换句话说,若 \(t_1=3k(k\in\mathbb N_+)\),那么 \(t_2=t_1+9\),这就导致 \(3\mid t_2\)。以此类推,会发现对于 \(\forall i\in[2,n]\),满足 \(t_i-t_{i-1}=9\),答案就是 \(…...
服务器安装docker、mysql、redis、nginx、nacos、jdk等
一、安装docker 1.1、安装必要工具 sudo yum install -y yum-utils \ device-mapper-persistent-data \ lvm21.2、进行仓库源设置 sudo yum-config-manager \ --add-repo \ https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo1.3、docker安装安装最新…...
StringComparer.OrdinalIgnoreCase
StringComparer.OrdinalIgnoreCase 是 .NET 提供的不区分大小写、且按 Unicode 码位排序的字符串比较器,适用于哈希表、字典、集合、排序等需要显式指定比较规则的地方。1. 核心特点特性说明比较规则 不区分大小写(A == a)排序规则 纯 Unicode 码位顺序(文化无关)性能 比文…...
LLM大模型:Qwen3-Next-80B中的next究竟是个啥?
1、近期,国内LLM头号玩家阿里发布了Qwen3-Next-80B模型,但从名字上看就和其之前发布的模型不同:多了next!这就奇怪了:为啥会多出一个next?这个next究竟是啥意思了?2、自从3年前 chatGPT 3.5发布后,AI又开始大火,就是因为效果比传统的机器学习好10倍!效果为啥好了,核…...
中了勒索病毒 peng
中了勒索病毒 peng一,中招 早上一上班,看到电脑屏幕显示这样的壁纸。 居然中招了?不敢相信。 我发现自己的网盘里的所有文件,都被加密并改名,形如 aaaa.jpg.[[VlDy9dk2RaQ1F]].[[Ruiz@firemail.cc]].peng 而且这些文件,都已同步到了网盘,通过手机app访问,也只能看到这些…...
在 WSL 中通过 Bash 函数快速转换 Windows 路径为 Ansible/WSL 路径 - 教程
在 WSL 中通过 Bash 函数快速转换 Windows 路径为 Ansible/WSL 路径 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Cour…...
金融租赁公司厂商租赁业务调研报告
厂商租赁金融租赁公司厂商租赁业务调研报告 报告摘要 本报告旨在全面、深入地分析中国金融租赁公司(下称“金租公司”)厂商租赁业务的现状、模式、市场环境、监管动态、数字化转型路径及绩效评估体系。截至2025年,厂商租赁作为一种深度绑定产业的业务模式,正日益成为金租公…...
普科科技PKC7030H交直流电流探头应用指南
普科PKC7030H探头支持DC-120MHz带宽、1%精度,30A连续电流测量,适用于高频大电流交直流混合信号测试。在现代电力电子、新能源及高速数字系统的设计与调试中,对复杂电流波形的精准测量是分析效率、优化性能与保障可靠性的基石。普科科技(PRBTEK)PKC7030H高频交直流电流…...
从“分散”到“统一”,中控技术利用SeaTunnel构建高效数据采集框架,核心数据同步任务0故障运行!
本文将深入探讨中控技术基于 Apache SeaTunnel 构建企业级数据采集框架的实践,重点分享集群高可用配置、性能调优、容错机制及数据质量监控等方面的具体思考与方案。作者 | 崔俊乐引言:对企业而言,数据采集的核心挑战从来不仅仅是“同步”,而是如何在大规模、多元异构的复杂…...
再见 Cursor,Qoder 真香!这波要改写 AI 编程格局
如果把未来 AI 编程工具的核心竞争力用一句话总结,那就是:能不能让开发者在透明化的协作中,信任它、依赖它,并且和它一起把项目养大。作者:loonggg 真心建议大家去使用一下这段时间最新推出的一款 AI 编程工具:Qoder 。 真的是太好用了,一点也不比 Cursor 差。 为什么这…...
T/B cell subtype marker - un
B cell ref: https://www.abcam.cn/primary-antibodies/b-cells-basic-immunophenotypingT cell ref: https://www.abcam.cn/primary-antibodies/t-cells-basic-immunophenotyping作者:un-define出处:https://www.cnblogs.com/mmtinfo/p/19099331本文版权归作者和博客园共有,…...
SAP FICO 完全凭证替代
GGB1 这个参数是获取所有行项目的关键USING bool_data TYPE gb002_015*&---------------------------------------------------------------------* *& Form u902 *&---------------------------------------------------------------------* * text *…...
K8s Application模式下的flink任务执行精要
本文分享自天翼云开发者社区《K8s Application模式下的flink任务执行精要》,作者:l****n 构键k8s集群在这里,我们需要搭建一个K8S环境用于提供flink任务的运行时环境。在这里推荐使用kubeadm或者一些脚本工具搭建,可参考本自动k8s脚本工具。具体过程在这里省略,可以参考上…...
从0打造一个TTS语音合成引擎:原理与实现
语音合成技术(Text-to-Speech, TTS)近年来发展迅猛,从早期机械感十足的合成音到如今几乎可以以假乱真的人声,背后是深度学习技术的巨大进步。本文将带你了解现代语音合成的基本原理,并尝试用Python实现一个简易版的TTS系统。 语音合成技术演进图1:语音合成技术发展历程,…...
莫队
Argvchs 说我不会根号算法,把之前的博客搬过来,然后再补点东西。 一种离线算法,可以用 \(O(n\sqrt n)\) 的复杂度处理区间查询问题,当然,也可以带修,下文也会提到。 关于复杂度 莫队优化的关键是排序 + 分块,将每个询问离线下来,按照左端点所在块从小到大排序,假如左端…...
Java基本语句-分支语句
Java基本语句-分支语句Day05 如何在API字典中寻找自己想要的Scanner类型 1.点击搜索 输入Scanner 2.字典中回显示各种类型的获取方式: nextByte()、nextShort()、nextInt()、nextLong()、nextdouble()、nextFloat()、next()多种引用使用。 3.调用Scanner类的相关方法,来获取指定…...
丘成桐谈AI
很多重要的科学发现,是在平凡的事情里面突然有个突破。 观念上的突破,在我看人工智能有困难做不到,现在全民学人工智能, 听起来很好听,但是师资不够, 跟数学的整个合作是刚开始, AI看见万千数据 记者:您第一次感觉到AI的冲击时什么时候 Yau:哈哈我坦白跟你讲,我从来没…...
异常检测在网络安全中的应用 - 实践
异常检测在网络安全中的应用 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; …...
大文件分片上传
分片:// 获取文件对象const inputFile = document.querySelector(input[type="file"]);// 设置分片大小:5MBconst CHUNK_SIZE = 5 * 1024 * 1024;// 文件上传事件inputFile.onchange = async (e) => {// 获取文件信息const file = e.target.files[0];// 获取文件…...
人小鼠免疫细胞maker基因 - un
人小鼠ref:https://www.abcam.cn/primary-antibodies/immune-cell-markers-poster作者:un-define出处:https://www.cnblogs.com/mmtinfo/p/19099316本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究…...
HyperWorks许可配置
在工程设计和仿真领域,正确的软件许可配置是确保工作流程顺畅、提高生产效率和实现最佳投资回报的关键。HyperWorks作为业界领先的工程仿真软件,其灵活的许可配置功能为用户提供了广泛的定制选项,确保软件能够完全满足各种业务需求。 什么是HyperWorks许可配置? HyperWorks…...
国标GB28181视频平台EasyGBS如何解决安防视频融合与级联管理的核心痛点?
国标GB28181视频平台EasyGBS如何解决安防视频融合与级联管理的核心痛点?在平安城市、雪亮工程等大型安防项目中,如何解决不同品牌设备与平台之间的互联互通难题?本文深度解析基于国标GB/T28181协议的EasyGBS视频平台的核心特点与技术优势,阐述其如何通过标准化协议,实现大…...
python基础-推导式
1.列表推导式 : 有规律的快速创建或者控制列表1.1 创建列表 eg: list1 = [ i for i in range(10)]1.2 带条件判断的列表推导式eg: list1 = [ i for i in range(50) if i % 3 == 0]3.多个for循环实现的列表推导式eg: list1 = [(item1, item2) for item1 in list2 for item2 in…...
人 CD 抗原完全指南 - un
设立分化簇 (CD) 命名系统的目的是对白细胞表面抗原进行分类。 最初,表面抗原是根据与它们结合的对应单克隆抗体进行命名。随着各实验室逐渐发现抗原常能刺激产生多种单克隆抗体,因此需要采用一种统一的命名系统。1982 年于巴黎举行的第 1 届国际人类白细胞分化抗原专题讨论会…...
Java入门知识
Java的特性和优势 简单性 面向对象 可移植性 (“Write once ,run anywhere”) 高性能 分布式 动态性 (反射机制) 多线程 (同时进行) 安全性 (异常机制,防病毒防篡改) 健壮性 在学习过程中爱上它,能够不断主动学习 在机遇来临之前,不断健壮自己 Java的三大版本 “Wri…...
AUTOSAR网络管理
汽车行业的网络管理一般有两种,一种是AutoSar另一种是OSEK,为啥汽车要网络管理,其实是为了降低车辆电池消耗,当车辆不工作时所有总线上的ECU通讯模块或整个ECU处于低功耗状态。网络管理一般用在电池供电的ECU,比如车上CAN上的ECU。为了避免通讯错误,需要网络管理来协调网…...
写用例注意点
写用例注意点: 1、测试标题 明确测试点 2、写用例的前几条用例都是主要场景的用例先写 微信个人能发微信红包 微信群发能发拼手气红包 微信群发能发拼手气红包 微信群发能发专属气红包 3、测试标题尽量写内容不要写案例: 例如验证标题能修改密码为:6666 4、相同的模块可以进…...
12 路低延迟推流!米尔 RK3576 赋能智能安防 360 环视
在智慧城市建设加速与社区安防需求升级的双重驱动下,“360 无死角监控 + 实时响应” 已成为安防领域的核心诉求。传统监控方案常受限于摄像头接入数量不足、编解码效率低、推流延迟高三大痛点,难以覆盖社区、园区等复杂场景的全点位监控,更无法满足应急事件 “毫秒级响应” …...
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?摘要 A公司的面经JVM的类加载的过程是怎么样的? 双亲委派模型的优点和缺点? 产生fullGC的情况有哪些? spring的动态代…...
Alternating Subsequence
CF1343C Alternating Subsequence 题目描述 回忆一下,如果序列 \(b\) 是序列 \(a\) 的一个子序列,那么 \(b\) 可以通过从 \(a\) 中删除零个或多个元素(不改变剩余元素的顺序)得到。例如,如果 \(a=[1, 2, 1, 3, 1, 2, 1]\),那么可能的子序列有:\([1, 1, 1, 1]\),\([3]\)…...
白鲸开源“创客北京2025”再摘殊荣,聚焦Agentic AI时代数据基础设施建设
近日,“创客北京2025”创新创业大赛海淀区级赛圆满落幕,经过最终比拼,北京白鲸开源科技有限公司凭借 「Agentic AI时代下的数据基础设施平台」(白鲸数据集成调度平台/WhaleStudio) 脱颖而出,荣获企业组二等奖。近日,“创客北京2025”创新创业大赛海淀区级赛圆满落幕,经…...
python基础-公共操作
数据类型间公共支持的操作符运算: + ,* ,in , not in‘+’ :支持的容器类型 字符串、列表、元组 ,实现两个容器的合并‘*’ : 支持的容器类型 字符串、列表、元组, 赋值容器内容str1 = q str1* 5 =qqqqqlist1 = [hello] list1*5 = [hello, hello, hello,…...
天翼云第九代弹性云主机:让每一次计算快人一步
随着数字化转型进程不断深入,云计算已成为推动千行百业智能化升级的核心引擎。弹性计算服务凭借其灵活扩展、高可用和高性能等特点,正持续为企业提供关键基础设施支持。面对日益复杂的业务场景与持续增长的计算需求,天翼云始终致力于通过持续创新和技术升级,推动弹性计算服…...
若依(RuoYi)框架漏洞总结
0x01 特征 绿若依 icon_hash=”706913071”蓝若依 icon_hash=” -1231872293”0x02 漏洞 弱口令 用户:admin ruoyi druid 密码:123456 admin druid admin123 admin888若依前台默认shiro key命令执行漏洞 若依默认使用shiro组件,所以可以试试shiro经典的remember…...
第一次个人项目作业_论文查重
第一次项目作业这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477这个作业的目标 实现一个3000字以上论文查重程序github连接:…...
2025年版《中科院期刊分区表》与2023年版对比表,附名单可直接查阅
2025年版《中科院期刊分区表》与2023年版相比,主要有以下几个变化: 1、发布时间提前:2025年版分区表从12月提前至3月发布,与投稿周期同步,学者可以尽早锁定期刊最新分区,避免“投稿后降区”的风险。 2、增加ESCI期刊收录:2025年版分区表增加了ESCI期刊的收录…...
对马岛之魂
护身符 稻荷神护身符----增加资源的获取 aa...
2019年双因素认证最佳实践指南
本文深入探讨2019年双因素认证的正确实现方式,对比TOTP与WebAuthn技术优劣,分析用户行为模式,并提供实际部署建议,帮助开发者构建更安全的认证系统。2019年正确实现双因素认证 - Trail of Bits博客 自3月起,Trail of Bits一直与Python软件基金会合作,为Warehouse(PyPI的…...
Account Kit(华为账号服务)再进化,开发者接入效率飙升!
Hi 各位开发者朋友~👋 为持续优化开发体验,提升集成效率,Account Kit接入体验再升级,助力构建更流畅、更安全的登录体验,让开发效率火力全开!😎 【体验升级】华为账号相关权益申请入口统一迁移至AGC华为账号一键登录权益实时审批华为账号一键登录支持三方开发框架01 …...
软件工程个人项目
软件工程个人项目3123004548软件工程个人项目这个作业属于哪个课程 <https://edu.cnblogs.com/campus/gdgy/SoftwareEngineering2024>这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477这个作业的目标 <设计一个…...
学习道路道阻且长 希望自己坚持下去
本人是一名专升本的大三学生 现在专业是软件工程专业 从今天开始学习java 翻了一下资料 发现很多人建议从前端开始学习 在专科学习中 也学过相应的基础知识,不过遗忘程度可能有点严重。对于语言的基本语法掌握需要加强巩固,希望自己好好坚持下去,努力学习。...
2025/9/18 总结
A 用时:2h 预期:100pts 实际:100pts 求出前缀和,\(s_k+s_i \text{xor} s_k\),考虑从高到低贪心,如果 \(s_i\) 的 \(j\) 位为 \(1\),不管如何贡献都有 \(2^j\),如果 \(s_i\) 的第 \(j\) 位为 \(0\),则 \(s_k\) 的第 \(j\) 位为 \(1\) 有 \(2_{j+1}\) 贡献,用高维前缀和…...
P2216 [HAOI2007] 理想的正方形
P2216 [HAOI2007] 理想的正方形#include <bits/stdc++.h> using namespace std;const int maxn = 1e3 + 10; int a,b,n; int c[maxn][maxn]; deque <int> dq1,dq2; int max1[maxn][maxn],min1[maxn][maxn]; int max2[maxn][maxn],min2[maxn][maxn];int ans = 2e9;i…...
PuTTY下载和安装
下载地址: https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html更改安装路径创建桌面快捷方式...
数据通路-单总线结构(最头晕的一集)
数据通路就是数据在各个部件之间传输的路径(包括路径上的部件) 控制信号是有控制部件产生的 数据通路的结构 1cpu内部单总线方式 2cpu内部多总线方式 3专用数据通路方式 内部总线 是指同一个部件,如cpu内部链接各寄存器以及运算部件之间的总线; 系统总线 是指同一台计算机系…...
python基础篇-集合
集合 :集合内的数据不重复,但是数据是无序的创建集合 {} 或者set()注: 创建空集合只能用set(), 因为{}已经被字典占用了eg: s1 = {10, 20 ,40,30 }eg: s2 = set(abcdefg) :用set创建,序列会被拆开 = 》 {’a, b, c, d, e, f, g}集合的操作:1.增加s1.add() 增加单个数据…...
#egsg:在同一程序中比较-计算圆的面积
以下是一个同时使用easygui和pysimplegui实现的圆形面积计算程序,通过菜单让用户选择使用哪种GUI库: import math import easygui import PySimpleGUI as sgdef easygui_calculator():"""使用easygui实现的版本"""title = "圆形面积计算器…...