代码随想录算法训练营第四天| LeetCode 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 142.环形链表II
24. 两两交换链表中的节点
题目链接/文章讲解/视频讲解:https://programmercarl.com/0024.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.html
做题思路:
链表有指针指向,更改节点的指向也就达到了交换节点的目的,假设有节点0(虚拟头节点),节点1,节点2,节点3。让节点1和节点2进行交换,就是让节点0指向节点2,再让节点2指向节点1,再让节点1指向节点3。看视频就能知道卡哥的注意事项。
交换节点的函数代码:
1 bool swapPairs(LinkList &L){
2 LNode *dummyHead; // 设置一个虚拟头结点
3 dummyHead = L;// 将虚拟头结点指向链表,
4 LNode* cur = dummyHead; //
5 while(cur->next != NULL && cur->next->next != NULL)
6 {
7 LNode* tmp = cur->next; // 记录临时节点,比如,表示节点1
8 LNode* tmp1 = cur->next->next->next; // 记录临时节点,表示节点3
9
10 cur->next = cur->next->next; // 让节点0的下一个指向节点2,这个看的是指向
11 cur->next->next = tmp; // 让节点2的下一个指向temp,节点1,
12 cur->next->next->next = tmp1; // 让节点1的下一个指向节点3,现在顺序是:0,2,1,3
13
14 cur = cur->next->next; // cur移动两位,准备下一轮交换,刚开始是从节点1开始交换,之后从节点3开始交换
15 }
16 //dummyHead = dummyHead->next;//虚拟头节点不是真正的节点,它下一个才是,这里如果要看函数返回值是不是节点类型
17 return true;
18 }
这道题的完整代码
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 swapPairs(LinkList &L){ //交换节点
16 LNode *dummyHead; // 设置一个虚拟头结点
17 dummyHead = L;// 将虚拟头结点指向链表,
18 LNode* cur = dummyHead; //
19 while(cur->next != NULL && cur->next->next != NULL)
20 {
21 LNode* tmp = cur->next; // 记录临时节点,比如,表示节点1
22 LNode* tmp1 = cur->next->next->next; // 记录临时节点,表示节点3
23
24 cur->next = cur->next->next; // 让节点0的下一个指向节点2,这个看的是指向
25 cur->next->next = tmp; // 让节点2的下一个指向temp,节点1,
26 cur->next->next->next = tmp1; // 让节点1的下一个指向节点3,现在顺序是:0,2,1,3
27
28 cur = cur->next->next; // cur移动两位,准备下一轮交换,刚开始是从节点1开始交换,之后从节点3开始交换
29 }
30 //dummyHead = dummyHead->next;//虚拟头节点不是真正的节点,它下一个才是,这里如果要看函数返回值是不是节点类型
31 return true;
32 }
33 bool InsertList(LinkList &L, int i, int e) //在第 i 个位置插入元素 e (带头结点)
34 {
35 if(i < 1) //只能在头结点之后插入,头结点是第0个,之后是第1个
36 return false;
37 LNode *p; //LNode * 强调这是一个结点,声明一个结点,表示指针p指向当前找到的结点
38 p = L; //L是指向头结点的头指针,再把刚开始的指针p指向L,表示目前的指针p指向头结点
39 int j = 0;//记录当前p指向的是第几个结点,从头结点0开始
40 while(p!= NULL && j < i - 1) //循环找到第 i-1 个结点,j = i-1,不进入循环,内存满了,不进入循环
41 {
42 p = p->next; //p指向下一个结点
43 j++;
44 }
45 if(p==NULL) //i值不合法,如果有4个结点,那i不能等于6,因为 p = p->next,在第5个位置之后满了,p指向NULL,下一步p->next出错了,根本找不到表的某个位置
46 return false;
47 LNode *s = (LNode *)malloc(sizeof(LNode));//创建一个节点,这个节点的数据域就是e
48 s->data = e;
49 s->next = p->next; //自己画图理解,一开始p后的节点不是s,这里让p的下一个节点变成了在s的下一个节点
50 p->next = s; //将结点s连到p之后
51 return true; //插入成功
52 }
53 void ShowList(LinkList L) //显示
54 {
55 if(L->next == NULL)
56 printf("这是一个空表\n");
57 while(L != NULL)
58 {
59 L = L->next;
60 printf("%d ",L->data);
61 }
62
63 }
64 bool DeleteList(LinkList &L, int val)//删除表L中值为 val 的元素
65 {
66 LNode *p = L->next, *q = L, *s; //L是头节点,L->next是第一个有效节点
67
68 while(p != NULL) //从第一个有效节点开始循环找值为val的节点
69 {
70 if(p->data == val)
71 {//原本顺序 p,q,s
72 s = p; //找到要删除的节点的话,就先用节点s保存p ,
73 p = p->next;//更改p的指向,在删除思路里说过,现在顺序 s,q,p
74 q->next = p;//q的指向也要记得改
75 free(s); //释放原来p所在的节点,只不过现在变成了s
76 }
77 else
78 {
79 q = p;//没有找到的话,就依次遍历其他节点,有种交换的感觉
80 p = p->next;
81 }
82 }
83 return true;
84 }
85 int GetList(LinkList &L, int i)//按位查找,返回第 i 个元素(带头节点)
86 {
87 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
88 return 0;
89 LNode *p;
90 int j = 0;
91 p = L;
92 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
93 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
94 p = p->next;
95 j++;
96 }
97 return p->data;
98 } //平均时间复杂度为O(n)
99 int UpdateList(LinkList &L, int i, int num)
100 {
101 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
102 return 0;
103 LNode *p;
104 int j = 0;
105 p = L;
106 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
107 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
108 p = p->next;
109 j++;
110 }
111 p->data = num;
112 return p->data;
113 }
114 void Destroy(LinkList &L) //销毁单链表
115 {
116 LNode *p;
117 while(L != NULL)
118 {
119 p = L;
120 L = L->next;
121 free(p);
122 }
123 }
124 void ShowMenu()
125 {
126 printf("************************\n");
127 printf("***** 1,添加数据 *****\n");
128 printf("***** 2,删除数据 *****\n");
129 printf("***** 3,查找数据 *****\n");
130 printf("***** 4,修改数据 *****\n");
131 printf("***** 5,显示数据 *****\n");
132 printf("***** 6,交换节点 *****\n");
133 printf("***** 0,销毁并退出 ***\n");
134 }
135 int main()
136 {
137 LinkList L;//声明一个单链表
138 InitList(L);//初始化函数调用
139 int n = 0;
140 int num = 0;
141 int select = 0; //创建选择输入的变量
142 while (true)
143 {
144 //菜单调用
145 ShowMenu();
146 printf("请输入你的选择\n");
147 scanf("%d", &select);
148 switch (select)
149 {
150 case 1: //1,添加数据
151 printf("请输入元素个数:\n");
152 scanf("%d",&n);
153 printf("请添加元素:\n");
154 for(int i = 1; i <= n; i++)
155 {
156 scanf("%d",&num);
157 InsertList(L,i,num);
158 }
159 printf("数据添加成功!\n");
160 getchar();
161 break;
162 case 2: //2,删除数据
163 int val;
164 scanf("%d",&val);
165 DeleteList(L,val);
166 getchar();
167 break;
168 case 3: //3,查找数据
169 printf("查找的元素是:%d\n",GetList(L,1));
170 getchar();
171 break;
172 case 4: //4,修改数据
173 UpdateList(L,1,0);
174 printf("修改成功!\n");
175 getchar();
176 break;
177 case 5: //4,显示数据
178 printf("单链表的数据有:");
179 ShowList(L);
180 getchar();
181 break;
182 case 6:
183 swapPairs(L);
184 getchar();
185 break;
186 case 0: //0,退出
187 Destroy(L);
188 printf("单链表已销毁!");
189 printf("欢迎下次使用");
190 return 0;
191 break;
192
193 default:
194 break;
195 }
196 }
197 return 0;
198 }
运行结果:
19.删除链表的倒数第N个节点
卡哥建议:双指针的操作,要注意,删除第N个节点,那么我们当前遍历的指针一定要指向 第N个节点的前一个节点,建议先看视频。
做题思路:
删除第几个节点会了,那删除倒数第几个节点,可以理解为,比如,有5个节点,1,2,3,4,5. 删除倒数第2个节点(4),那就是删除第 5-2+1个节点,即第4个节点(4)。
删除倒数第几个节点的代码:
1 bool DeleteList(LinkList &L,int n, int i)//删除表L中倒数第 i 个位置的元素
2 {
3 if(i < 1)
4 return false;
5 LNode *p;
6 p = L;
7 int j = 0;
8 while(p != NULL && j < n - i ) //循环找倒数第 i个节点。这里本来是 j<i-1,找第i-1个节点,代码做了改变
9 //比如,有5个节点,1,2,3,4,5. 删除倒数第2个节点(4),那就是删除第 5-2+1个节点,即第4个节点(4)。
10 {
11 p = p->next;
12 j++;
13 }
14 if(p == NULL) //i值不合法,导致第 n-i个节点根本在表中找不到
15 return false;
16 if(p->next == NULL) //第 n-i 个节点能找到,但是刚好第 n-i 个节点之后已无其他节点(第n-i-1结点和第n-i+1个节点没有了),那就没有办法把第 i-1 个节点的指向下一个节点的指针指向第 i+1 个节点
17 return false;
18 LNode *q = p->next; //p是第 n-i 个节点,p->next是之后的节点,也是第 n-i+1 个节点,令q指向被删除节点
19 p->next = q->next; //将*q 节点从链中断开,重新更改指针p的指向,指向第 n-i+1个节点
20 free(q); //释放节s点的存储空间
21 return true;
22 }
为了便于利用链表总节点数n,所以我在主函数里的switch结构里的case1,把删除函数加进去了,完整的代码:
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 n, int i)//删除表L中倒数第 i 个位置的元素
47 {
48 if(i < 1)
49 return false;
50 LNode *p;
51 p = L;
52 int j = 0;
53 while(p != NULL && j < n - i ) //循环找倒数第 i个节点。这里本来是 j<i-1,找第i-1个节点,代码做了改变
54 //比如,有5个节点,1,2,3,4,5. 删除倒数第2个节点(4),那就是删除第 5-2+1个节点,即第4个节点(4)。
55 {
56 p = p->next;
57 j++;
58 }
59 if(p == NULL) //i值不合法,导致第 n-i个节点根本在表中找不到
60 return false;
61 if(p->next == NULL) //第 n-i 个节点能找到,但是刚好第 n-i 个节点之后已无其他节点(第n-i-1结点和第n-i+1个节点没有了),那就没有办法把第 i-1 个节点的指向下一个节点的指针指向第 i+1 个节点
62 return false;
63 LNode *q = p->next; //p是第 n-i 个节点,p->next是之后的节点,也是第 n-i+1 个节点,令q指向被删除节点
64 p->next = q->next; //将*q 节点从链中断开,重新更改指针p的指向,指向第 n-i+1个节点
65 free(q); //释放节s点的存储空间
66 return true;
67 }
68 int GetList(LinkList &L, int i)//按位查找,返回第 i 个元素(带头节点)
69 {
70 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
71 return 0;
72 LNode *p;
73 int j = 0;
74 p = L;
75 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
76 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
77 p = p->next;
78 j++;
79 }
80 return p->data;
81 } //平均时间复杂度为O(n)
82 int UpdateList(LinkList &L, int i, int num)
83 {
84 if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
85 return 0;
86 LNode *p;
87 int j = 0;
88 p = L;
89 while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
90 { //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
91 p = p->next;
92 j++;
93 }
94 p->data = num;
95 return p->data;
96 }
97 void Destroy(LinkList &L) //销毁单链表
98 {
99 LNode *p;
100 while(L != NULL)
101 {
102 p = L;
103 L = L->next;
104 free(p);
105 }
106 }
107 void ShowMenu()
108 {
109 printf("************************\n");
110 printf("***** 1,添加并删除数据 *****\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 printf("请输入要删除倒数第几个数:\n");
142 int i;
143 scanf("%d",&i);
144 DeleteList(L,n,i);
145 getchar();
146 //break;
147 //getchar();
148 break;
149 //case 2: //2,删除数据
150
151 case 2: //2,查找数据
152 printf("查找的元素是:%d\n",GetList(L,1));
153 getchar();
154 break;
155 case 3: //3,修改数据
156 UpdateList(L,1,0);
157 printf("修改成功!\n");
158 getchar();
159 break;
160 case 4: //4,显示数据
161 printf("单链表的数据有:");
162 ShowList(L);
163 getchar();
164 break;
165 case 0: //0,退出
166 Destroy(L);
167 printf("单链表已销毁!");
168 printf("欢迎下次使用");
169 return 0;
170 break;
171
172 default:
173 break;
174 }
175 }
176 return 0;
177 }
运行结果:
142.环形链表II
卡哥建议:算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口,建议先看视频。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html
1,这个题先知道用快慢指针法做,如果把链表想象成一条直线,那快慢指针永远不会相遇,它们走过的路线合起来只能是直线;如果把链表想象成一条直线的末尾有个环,那快指针走得快,会先进入环里,等慢指针走到环入口,可能这时候快指针已经走了一会了也到环入口了,刚好和慢指针相遇,或者慢指针已经进入环了,快指针在环里也走得快,能追上慢指针,相遇。因为有环,所以相遇,因为相遇,所以有环。
2,这个看视频,会有一个公式,2(x+y) = xy + n(y+z),假设快指针速度是慢指针的2倍,那时间相同的情况下,列比例式,路程比速度。关于公式 x = (n-1)(y+z)+ z,是指快指针在走了n-1圈后,x = z,从相遇点走z,和从起点0走x,从视频中的图里就能看到刚好在环入口处。这个相遇点是已经相遇的地方,x=z,只是推导出的,不是两个指针重新去走的。
这个题的代码只写函数的,先没写完整的代码。
1 ListNode *detectCycle(ListNode *head) {
2 ListNode* fast = head;
3 ListNode* slow = head;
4 while(fast != NULL && fast->next != NULL) {
5 slow = slow->next;
6 fast = fast->next->next; //快指针比慢指针快2
7 // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
8 if (slow == fast) {
9 ListNode* index1 = fast;
10 ListNode* index2 = head;
11 while (index1 != index2) {//不是相遇点
12 index1 = index1->next; //继续向后找
13 index2 = index2->next;
14 }
15 return index2; // 返回环的入口
16 }
17 }
18 return NULL;
本文作者:romantichuaner
本文链接:https://www.cnblogs.com/romantichuaner/p/17591245.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步