代码随想录算法训练营第三天| LeetCode 203.移除链表元素(同时也对整个单链表进行增删改查操作) 707.设计链表 206.反转链表
203.移除链表元素
卡哥题目建议:本题最关键是要理解虚拟头结点的使用技巧,这个对链表题目很重要。
做题思路:
1,分为不带头节点的和带(虚拟)头节点的两种情况。
1.1 . 不带头节点就是直接在原链表上删除节点,删第1个节点之后的节点需要改一下指针指向,就是用文字描述,比如删第2个节点,让第1个节点的指向第3个节点,再释放第2个节点。同理,除了第1个有效节点即首节点外,其他节点都是通过前一个节点来删除当前节点,但是首节点没有前1个节点。所以首节点如何删除呢,其实只要将首节点的指针指向,向后移动一位就可以,这样就从链表中删除了一个首节点。
1.2. 带虚拟头节点就是在初始化链表的时候,创建出一个头节点,方便使原链表的所有节点就都可以按照统一的方式进行删除了。因为看 1.1 的思路,需要单独写一段逻辑来处理移除头结点的情况。
在后续代码中 ,使用LinkList,强调这是一个单链表; 使用LNode,强调这是一个节点。
这里的(用虚拟头节点)代码模仿了我写的博客:https://www.cnblogs.com/romantichuaner/p/17557080.html,我用C语言写的。根据这个题,作了删除函数的改变。
1 bool DeleteList(LinkList &L, int val)//删除表L中值为 val 的元素
2 {
3 LNode *p = L->next, *q = L, *s; //L是头节点,L->next是第一个有效节点
4
5 while(p != NULL) //从第一个有效节点开始循环找值为val的节点
6 {
7 if(p->data == val)
8 {//原本顺序 p,q,s
9 s = p; //找到要删除的节点的话,就先用节点s保存p ,
10 p = p->next;//更改p的指向,在删除思路里说过,现在顺序 s,q,p
11 q->next = p;//q的指向也要记得改
12 free(s); //释放原来p所在的节点,只不过现在变成了s
13 }
14 else
15 {
16 q = p;//没有找到的话,就依次遍历其他节点,有种交换的感觉
17 p = p->next;
18 }
19 }
20 return true;
21 }
完整代码的函数调用也改变了。
1 case 2: //2,删除数据
2 int val;
3 scanf("%d",&val);
4 DeleteList(L,val);
5 getchar();
6 break;
本题中是把链表中的元素键盘输入的,所以这里我还改了添加函数的调用。
1 case 1: //1,添加数据
2 printf("请输入元素个数:\n");
3 scanf("%d",&n);
4 printf("请添加元素:\n");
5 for(int i = 1; i <= n; i++)
6 {
7 scanf("%d",&num);
8 InsertList(L,i,num);
9 }
10 printf("数据添加成功!\n");
11 getchar();
12 break;
综上,本题完整代码以及拓展的一些操作代码如下:
1 #include <stdio.h>
2 #include <malloc.h>
3 typedef struct LNode{ //定义单链表结点类型 //这种方式代码可读性更强
4 int data;//每个节点存放一个数据元素
5 struct LNode *next;//指针指向下一个节点
6 }LNode, *LinkList; //LNode 是struct LNode 的别名,LinkList是struct LNode *的别名,指针指向整个结构体
7 bool InitList(LinkList &L) //初始化一个单链表(带头结点)
8 {
9 L = (LNode *)malloc(sizeof(LNode)); //分配一个头节点,malloc函数强调返回是一个节点(LNode *),如果单链表有头节点,则头指针指向头节点,即 LinkList L = (LNode *)malloc(sizeof(LNode)); 等号有指向作用
10 if(L == NULL) //内存不足,分配失败,就没有创建出单链表,因为已经分配了一个节点,所以只能内存不足的原因,空表是没有节点
11 return false;
12 L->next = NULL;//目前只有一个头节点,头节点之后暂时还没有节点
13 return true;
14 }
15 bool InsertList(LinkList &L, int i, int e) //在第 i 个位置插入元素 e (带头结点)
16 {
17 if(i < 1) //只能在头结点之后插入,头结点是第0个,之后是第1个
18 return false;
19 LNode *p; //LNode * 强调这是一个结点,声明一个结点,表示指针p指向当前找到的结点
20 p = L; //L是指向头结点的头指针,再把刚开始的指针p指向L,表示目前的指针p指向头结点
21 int j = 0;//记录当前p指向的是第几个结点,从头结点0开始
22 while(p!= NULL && j < i - 1) //循环找到第 i-1 个结点,j = i-1,不进入循环,内存满了,不进入循环
23 {
24 p = p->next; //p指向下一个结点
25 j++;
26 }
27 if(p==NULL) //i值不合法,如果有4个结点,那i不能等于6,因为 p = p->next,在第5个位置之后满了,p指向NULL,下一步p->next出错了,根本找不到表的某个位置
28 return false;
29 LNode *s = (LNode *)malloc(sizeof(LNode));//创建一个节点,这个节点的数据域就是e
30 s->data = e;
31 s->next = p->next; //自己画图理解,一开始p后的节点不是s,这里让p的下一个节点变成了在s的下一个节点
32 p->next = s; //将结点s连到p之后
33 return true; //插入成功
34 }
35 void ShowList(LinkList L) //显示
36 {
37 if(L->next == NULL)
38 printf("这是一个空表\n");
39 while(L != NULL)
40 {
41 L = L->next;
42 printf("%d ",L->data);
43 }
44
45 }
46 bool DeleteList(LinkList &L, int val)//删除表L中值为 val 的元素
47 {
48 LNode *p = L->next, *q = L, *s; //L是头节点,L->next是第一个有效节点
49
50 while(p != NULL) //从第一个有效节点开始循环找值为val的节点
51 {
52 if(p->data == val)
53 {//原本顺序 p,q,s
54 s = p; //找到要删除的节点的话,就先用节点s保存p ,
55 p = p->next;//更改p的指向,在删除思路里说过,现在顺序 s,q,p
56 q->next = p;//q的指向也要记得改
57 free(s); //释放原来p所在的节点,只不过现在变成了s
58 }
59 else
60 {
61 q = p;//没有找到的话,就依次遍历其他节点,有种交换的感觉
62 p = p->next;
63 }
64 }
65 return true;
66 }
67 int GetList(LinkList &L, int i)//按位查找,返回第 i 个元素(带头节点)
68 {
69 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
70 return 0;
71 LNode *p;
72 int j = 0;
73 p = L;
74 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
75 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
76 p = p->next;
77 j++;
78 }
79 return p->data;
80 } //平均时间复杂度为O(n)
81 int UpdateList(LinkList &L, int i, int num)
82 {
83 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
84 return 0;
85 LNode *p;
86 int j = 0;
87 p = L;
88 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
89 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
90 p = p->next;
91 j++;
92 }
93 p->data = num;
94 return p->data;
95 }
96 void Destroy(LinkList &L) //销毁单链表
97 {
98 LNode *p;
99 while(L != NULL)
100 {
101 p = L;
102 L = L->next;
103 free(p);
104 }
105 }
106 void ShowMenu()
107 {
108 printf("************************\n");
109 printf("***** 1,添加数据 *****\n");
110 printf("***** 2,删除数据 *****\n");
111 printf("***** 3,查找数据 *****\n");
112 printf("***** 4,修改数据 *****\n");
113 printf("***** 5,显示数据 *****\n");
114 printf("***** 0,销毁并退出 ***\n");
115 }
116 int main()
117 {
118 LinkList L;//声明一个单链表
119 InitList(L);//初始化函数调用
120 int n = 0;
121 int num = 0;
122 int select = 0; //创建选择输入的变量
123 while (true)
124 {
125 //菜单调用
126 ShowMenu();
127 printf("请输入你的选择\n");
128 scanf("%d", &select);
129 switch (select)
130 {
131 case 1: //1,添加数据
132 printf("请输入元素个数:\n");
133 scanf("%d",&n);
134 printf("请添加元素:\n");
135 for(int i = 1; i <= n; i++)
136 {
137 scanf("%d",&num);
138 InsertList(L,i,num);
139 }
140 printf("数据添加成功!\n");
141 getchar();
142 break;
143 case 2: //2,删除数据
144 int val;
145 scanf("%d",&val);
146 DeleteList(L,val);
147 getchar();
148 break;
149 case 3: //3,查找数据
150 printf("查找的元素是:%d\n",GetList(L,1));
151 getchar();
152 break;
153 case 4: //4,修改数据
154 UpdateList(L,1,0);
155 printf("修改成功!\n");
156 getchar();
157 break;
158 case 5: //4,显示数据
159 printf("单链表的数据有:");
160 ShowList(L);
161 getchar();
162 break;
163 case 0: //0,退出
164 Destroy(L);
165 printf("单链表已销毁!");
166 printf("欢迎下次使用");
167 return 0;
168 break;
169
170 default:
171 break;
172 }
173 }
174 return 0;
175 }
运行结果显示:
707.设计链表
题目链接/文章讲解/视频讲解:https://programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html
卡哥题目建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点
做题思路:
1,先看 题目中的 get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。我们需要找到前 index - 1 个节点。函数名自己一改就行。
1 int GetList(LinkList &L, int i)//按位查找,返回第 i 个元素(带头节点)
2 {
3 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
4 return -1;
5 LNode *p;
6 int j = 0;
7 p = L;
8 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
9 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
10 p = p->next;
11 j++;
12 }
13 return p->data;
14 } //平均时间复杂度为O(n)
函数调用。
1 case 3: //3,查找数据
2 printf("查找的元素是:%d\n",GetList(L,1));
3 getchar();
4 break;
运行结果显示:
2,addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。这个和头插法是一样的,看b站的视频吧
1 bool List_HeadInsert(LinkList &L)//逆向建立单链表
2 {
3 LNode *s;
4 int x;
5 L = (LNode *)malloc(sizeof(LNode)); //创建头结点
6 L->next = NULL; //初始为空链表,养成好习惯,只要是初始化单链表,都先把头指针指向NULL
7 scanf("%d",&x);
8 while(x != 9999)
9 {
10 s = (LNode *)malloc(sizeof(LNode)); //第10行到第13行代码和后插操作InsertNextNode函数第5行到第10行代码是等同的
11 s->data = x;
12 s->next = L->next;//把新结点插入到头结点之后
13 L->next = s;//更改头指针指向
14 scanf("%d",&x);
15 }
16 return true;
17 }
函数调用。注意要把主函数的初始化函数注释了,因为头插法和尾插法函数里就有初始化的一部分。
1 case 6: //头插法
2 List_HeadInsert(L);
3 getchar();
4 break;
运行结果显示:
3,addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。这个和尾插法是一样的。
1 LinkList List_TailInsert(LinkList &L) //正向建立单链表
2 {
3 int x; //插入的元素
4 L = (LinkList)malloc(sizeof(LNode));//建立头结点
5 LNode *s, *r = L; //声明结点,s是把插入的元素放在结点s,r为表尾指针,让s,r初始化指向,头结点
6 L->next = NULL; //初始为空链表
7 scanf("%d",&x);
8 while(x != 9999) //输入9999表示结束,任意的一个数字
9 {
10 s = (LNode *)malloc(sizeof(LNode)); //分配插入的结点
11 s->data = x;
12 r->next = s; //在r结点之后插入元素x
13 r = s; //永远保持r指向最后一个结点
14 scanf("%d",&x);
15 }
16 r->next = NULL; //插入数字完后,再把尾结点指针置空
17 return L; //返回链表L
18 } //时间复杂度为O(n)
1 case 7: //尾插法
2 List_TailInsert(L);
3 getchar();
4 break;
运行结果显示:
4,addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。模仿插入函数。
1 bool InsertList(LinkList &L, int i, int e) //在第 i 个位置插入元素 e (带头结点)
2 {
3 if(i < 1) //只能在头结点之后插入,头结点是第0个,之后是第1个
4 {
5 List_HeadInsert(L); //头插法
6 }
7
8 LNode *p; //LNode * 强调这是一个结点,声明一个结点,表示指针p指向当前找到的结点
9 p = L; //L是指向头结点的头指针,再把刚开始的指针p指向L,表示目前的指针p指向头结点
10 int j = 0;//记录当前p指向的是第几个结点,从头结点0开始
11 while(p!= NULL && j < i - 1) //循环找到第 i-1 个结点,j = i-1,不进入循环,内存满了,不进入循环
12 {
13 p = p->next; //p指向下一个结点
14 j++;
15 }
16 if(p==NULL) //i值不合法,如果有4个结点,那i不能等于6,因为 p = p->next,在第5个位置之后满了,p指向NULL,下一步p->next出错了,根本找不到表的某个位置
17 return false;
18 LNode *s = (LNode *)malloc(sizeof(LNode));//创建一个节点,这个节点的数据域就是e
19 s->data = e;
20 s->next = p->next; //自己画图理解,一开始p后的节点不是s,这里让p的下一个节点变成了在s的下一个节点
21 p->next = s; //将结点s连到p之后
22 return true; //插入成功
23 }
函数调用。注意:调用之间要把主函数的初始化函数注释打开。
1 case 1: //1,添加数据
2 InsertList(L,1,1);
3 InsertList(L,2,2);
4 InsertList(L,3,3);
5 InsertList(L,4,4);
6 InsertList(L,5,5);
7 printf("数据添加成功!\n");
8 getchar();
9 break;
运行结果显示:
5 ,deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。删除思路和今天的第一道一样。
1 bool DeleteList(LinkList &L, int i)//删除表L中第 i 个位置的元素,并用e返回删除元素的值。
2 {
3 if(i < 1)
4 return false;
5 LNode *p;
6 p = L;
7 int j = 0;
8 while(p != NULL && j < i - 1) //循环找第 i-1 个节点
9 {
10 p = p->next;
11 j++;
12 }
13 if(p == NULL) //i值不合法,导致第 i-1 个节点根本在表中找不到
14 return false;
15 if(p->next == NULL) //第 i-1 个节点能找到,但是刚好第 i-1 个节点之后已无其他节点(第i个结点和第i+1个节点没有了),那就没有办法把第 i-1 个节点的指向下一个节点的指针指向第 i+1 个节点
16 return false;
17 LNode *q = p->next; //p是第 i-1 个节点,p->next是之后的节点,也是第 i 个节点,令q指向被删除节点
18 p->next = q->next; //将*q 节点从链中断开,重新更改指针p的指向,指向第 i + 1个节点
19 free(q); //释放节s点的存储空间
20 return true;
21 }
1 case 2: //2,删除数据
2 DeleteList(L,1);
3 getchar();
4 break;
完整代码实现:
1 #include <stdio.h>
2 #include <malloc.h>
3 typedef struct LNode{ //定义单链表结点类型 //这种方式代码可读性更强
4 int data;//每个节点存放一个数据元素
5 struct LNode *next;//指针指向下一个节点
6 }LNode, *LinkList; //LNode 是struct LNode 的别名,LinkList是struct LNode *的别名,指针指向整个结构体
7 bool InitList(LinkList &L) //初始化一个单链表(带头结点)
8 {
9 L = (LNode *)malloc(sizeof(LNode)); //分配一个头节点,malloc函数强调返回是一个节点(LNode *),如果单链表有头节点,则头指针指向头节点,即 LinkList L = (LNode *)malloc(sizeof(LNode)); 等号有指向作用
10 if(L == NULL) //内存不足,分配失败,就没有创建出单链表,因为已经分配了一个节点,所以只能内存不足的原因,空表是没有节点
11 return false;
12 L->next = NULL;//目前只有一个头节点,头节点之后暂时还没有节点
13 return true;
14 }
15 bool List_HeadInsert(LinkList &L)//逆向建立单链表
16 {
17 LNode *s;
18 int x;
19 L = (LNode *)malloc(sizeof(LNode)); //创建头结点
20 L->next = NULL; //初始为空链表,养成好习惯,只要是初始化单链表,都先把头指针指向NULL
21 scanf("%d",&x);
22 while(x != 9999)
23 {
24 s = (LNode *)malloc(sizeof(LNode)); //第10行到第13行代码和后插操作InsertNextNode函数第5行到第10行代码是等同的
25 s->data = x;
26 s->next = L->next;//把新结点插入到头结点之后
27 L->next = s;//更改头指针指向
28 scanf("%d",&x);
29 }
30 return true;
31 }
32 bool List_TailInsert(LinkList &L) //正向建立单链表
33 {
34 int x; //插入的元素
35 L = (LinkList)malloc(sizeof(LNode));//建立头结点
36 LNode *s, *r = L; //声明结点,s是把插入的元素放在结点s,r为表尾指针,让s,r初始化指向,头结点
37 L->next = NULL; //初始为空链表
38 scanf("%d",&x);
39 while(x != 9999) //输入9999表示结束,任意的一个数字
40 {
41 s = (LNode *)malloc(sizeof(LNode)); //分配插入的结点
42 s->data = x;
43 r->next = s; //在r结点之后插入元素x
44 r = s; //永远保持r指向最后一个结点
45 scanf("%d",&x);
46 }
47 r->next = NULL; //插入数字完后,再把尾结点指针置空
48 return true; //返回链表L
49 } //时间复杂度为O(n)
50 bool InsertList(LinkList &L, int i, int e) //在第 i 个位置插入元素 e (带头结点)
51 {
52 if(i < 1) //只能在头结点之后插入,头结点是第0个,之后是第1个
53 {
54 List_HeadInsert(L); //头插法
55 }
56
57 LNode *p; //LNode * 强调这是一个结点,声明一个结点,表示指针p指向当前找到的结点
58 p = L; //L是指向头结点的头指针,再把刚开始的指针p指向L,表示目前的指针p指向头结点
59 int j = 0;//记录当前p指向的是第几个结点,从头结点0开始
60 while(p!= NULL && j < i - 1) //循环找到第 i-1 个结点,j = i-1,不进入循环,内存满了,不进入循环
61 {
62 p = p->next; //p指向下一个结点
63 j++;
64 }
65 if(p==NULL) //i值不合法,如果有4个结点,那i不能等于6,因为 p = p->next,在第5个位置之后满了,p指向NULL,下一步p->next出错了,根本找不到表的某个位置
66 return false;
67 LNode *s = (LNode *)malloc(sizeof(LNode));//创建一个节点,这个节点的数据域就是e
68 s->data = e;
69 s->next = p->next; //自己画图理解,一开始p后的节点不是s,这里让p的下一个节点变成了在s的下一个节点
70 p->next = s; //将结点s连到p之后
71 return true; //插入成功
72 }
73 void ShowList(LinkList L) //显示
74 {
75 if(L->next == NULL)
76 printf("这是一个空表\n");
77 while(L != NULL)
78 {
79 L = L->next;
80 printf("%d ",L->data);
81 }
82
83 }
84 bool DeleteList(LinkList &L, int i)//删除表L中第 i 个位置的元素,并用e返回删除元素的值。
85 {
86 if(i < 1)
87 return false;
88 LNode *p;
89 p = L;
90 int j = 0;
91 while(p != NULL && j < i - 1) //循环找第 i-1 个节点
92 {
93 p = p->next;
94 j++;
95 }
96 if(p == NULL) //i值不合法,导致第 i-1 个节点根本在表中找不到
97 return false;
98 if(p->next == NULL) //第 i-1 个节点能找到,但是刚好第 i-1 个节点之后已无其他节点(第i个结点和第i+1个节点没有了),那就没有办法把第 i-1 个节点的指向下一个节点的指针指向第 i+1 个节点
99 return false;
100 LNode *q = p->next; //p是第 i-1 个节点,p->next是之后的节点,也是第 i 个节点,令q指向被删除节点
101 p->next = q->next; //将*q 节点从链中断开,重新更改指针p的指向,指向第 i + 1个节点
102 free(q); //释放节s点的存储空间
103 return true;
104 }
105 int GetList(LinkList &L, int i)//按位查找,返回第 i 个元素(带头节点)
106 {
107 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
108 return -1;
109 LNode *p;
110 int j = 0;
111 p = L;
112 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
113 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
114 p = p->next;
115 j++;
116 }
117 return p->data;
118 } //平均时间复杂度为O(n)
119 int UpdateList(LinkList &L, int i, int num)
120 {
121 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
122 return 0;
123 LNode *p;
124 int j = 0;
125 p = L;
126 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
127 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
128 p = p->next;
129 j++;
130 }
131 p->data = num;
132 return p->data;
133 }
134 void Destroy(LinkList &L) //销毁单链表
135 {
136 LNode *p;
137 while(L != NULL)
138 {
139 p = L;
140 L = L->next;
141 free(p);
142 }
143 }
144 void ShowMenu()
145 {
146 printf("************************\n");
147 printf("***** 1,添加数据 *****\n");
148 printf("***** 2,删除数据 *****\n");
149 printf("***** 3,查找数据 *****\n");
150 printf("***** 4,修改数据 *****\n");
151 printf("***** 5,显示数据 *****\n");
152 printf("***** 6,头插法 *****\n");
153 printf("***** 7,尾插法 *****\n");
154 printf("***** 0,销毁并退出 ***\n");
155 }
156 int main()
157 {
158 LinkList L;//声明一个单链表
159 InitList(L);//初始化函数调用
160
161 int select = 0; //创建选择输入的变量
162 while (true)
163 {
164 //菜单调用
165 ShowMenu();
166 printf("请输入你的选择\n");
167 scanf("%d", &select);
168 switch (select)
169 {
170 case 1: //1,添加数据
171 InsertList(L,1,1);
172 InsertList(L,2,2);
173 InsertList(L,3,3);
174 InsertList(L,4,4);
175 InsertList(L,5,5);
176 printf("数据添加成功!\n");
177 getchar();
178 break;
179 case 2: //2,删除数据
180 DeleteList(L,1);
181 getchar();
182 break;
183 case 3: //3,查找数据
184 printf("查找的元素是:%d\n",GetList(L,1));
185 getchar();
186 break;
187 case 4: //4,修改数据
188 UpdateList(L,1,0);
189 printf("修改成功!\n");
190 getchar();
191 break;
192 case 5: //4,显示数据
193 printf("单链表的数据有:");
194 ShowList(L);
195 getchar();
196 break;
197 case 6: //头插法
198 List_HeadInsert(L);
199 getchar();
200 break;
201 case 7: //尾插法
202 List_TailInsert(L);
203 getchar();
204 break;
205 case 0: //0,退出
206 Destroy(L);
207 printf("单链表已销毁!");
208 printf("欢迎下次使用");
209 return 0;
210 break;
211
212 default:
213 break;
214 }
215 }
216 return 0;
217 }
206.反转链表
题目链接/文章讲解/视频讲解:https://programmercarl.com/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.html
链表的反转就是链表的逆置,从上个题就能看出,头插法是链表的逆置。
代码见上个题的。
本文作者:银河小船儿
本文链接:https://www.cnblogs.com/romantichuaner/p/17587197.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步