forked from dairongpeng/algorithm-note
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path25-算法面试专题-链表.md
More file actions
358 lines (304 loc) · 9.74 KB
/
Copy path25-算法面试专题-链表.md
File metadata and controls
358 lines (304 loc) · 9.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
```java
package com.xiaodai.algorithm;
/**
* Author :dai
* Date :2020/12/25 5:04 下午
* Description:
*/
public class LinkedListUtil {
/**
* 链表的节点,可实现成泛型
*/
public static class Node {
public int value;
public Node next;
public Node(int data) {
value = data;
}
}
/**
* 双向列表的节点结构,可实现成泛型
*/
public static class DoubleNode {
public int value;
public DoubleNode last;
public DoubleNode next;
public DoubleNode(int data) {
value = data;
}
}
/**
* 1、检测链表是否成环。返回成环是否,第一次相遇并不保证是成环的节点
*
* @param head
* @return
*/
public boolean hasCycle(Node head) {
if (head == null || head.next == null) {
return false;
}
Node slow = head;
Node fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
// 有环的话一定追的上,但不一定是第一次成环的节点
return true;
}
/**
* 2、传入头节点,翻转单项链表
*
* @param head
* @return
*/
public static Node reverseLinkedList(Node head) {
Node pre = null;
Node next = null;
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
/**
* 3、移除链表中等于值的节点
* <p>
* 例如:1->2->3->3->4->5->3, 和 val = 3, 你需要返回删除3之后的链表:1->2->4->5。
*
* @param head
* @param num
* @return
*/
public static Node removeValue(Node head, int num) {
// 从链表的头开始,舍弃掉开头的且连续的等于num的节点
while (head != null) {
if (head.value != num) {
break;
}
head = head.next;
}
// head来到 第一个不需要删的位置
Node pre = head;
Node cur = head;
// 快慢指针
while (cur != null) {
if (cur.value == num) { // 快指针cur向下滑动,如果值等于num,则暂时把下一个节点给慢指针的下一个指向。从而跳过等于num的节点
pre.next = cur.next;
} else { // cur此时到了不等于num的节点,则慢指针追赶上去。达到的效果就是等于num的节点都被删掉了
pre = cur;
}
// 快指针向下滑动
cur = cur.next;
}
return head;
}
/**
* 4、打印两个有序链表的公共部分
* 例如:head1: 1->2->3->3->4->5 head2: 0->0->1->2->3->3->7->9
* 公共部分为:1 2 3 3
*
* @param head1
* @param head2
*/
public void printCommonPart(Node head1, Node head2) {
System.out.println("Common Part: ");
while (head1 != null && head2 != null) {
if (head1.value < head2.value) {
head1 = head1.next;
} else if (head1.value > head2.value) {
head2 = head2.next;
} else {
System.out.println(head1.value);
head1 = head1.next;
head2 = head2.next;
}
}
System.out.println();
}
/**
* 5、删除单链表的倒数第k个节点
*
* @param head
* @param lastKth
* @return
*/
public Node removeLastKthNode(Node head, int lastKth) {
if (head == null || lastKth < 1) {
return head;
}
// cur指针也指向链表头节点
Node cur = head;
// 检查倒数第lastKth个节点的合法性
while (cur != null) {
lastKth--;
cur = cur.next;
}
// 需要删除的是头结点
if (lastKth == 0) {
head = head.next;
}
if (lastKth < 0) {
// cur回到头结点
cur = head;
while (++lastKth != 0) {
cur = cur.next;
}
// 次吃cur就是要删除的前一个节点。把原cur.next删除
cur.next = cur.next.next;
}
// lastKth > 0的情况,表示倒数第lastKth节点比原链表程度要大,即不存在
return head;
}
/**
* 6、删除链表中间节点
* 思路:如果链表为空或者只有一个节点,不做处理。链表两个节点删除第一个节点,链表三个节点,删除中间第二个节点,链表四个节点,删除上中点
*
* @param head
* @return
*/
public Node removeMidNode(Node head) {
// 无节点,或者只有一个节点的情况,直接返回
if (head == null || head.next == null) {
return head;
}
// 链表两个节点,删除第一个节点
if (head.next.next == null) {
return head.next;
}
Node pre = head;
Node cur = head.next.next;
// 快慢指针
if (cur.next != null && cur.next.next != null) {
pre = pre.next;
cur = cur.next.next;
}
// 快指针走到尽头,慢指针奇数长度停留在中点,偶数长度停留在上中点。删除该节点
pre.next = pre.next.next;
return head;
}
/**
* 7、给定一个链表,如果成环,返回成环的那个节点
* <p>
* 思路:
* 1. 快慢指针fast和slow,开始时,fast和slow都指向头节点,fast每次走两步,slow每次走一步
* 2. 快指针向下移动的过程中,如果提前到达null,则链表无环,提前结束
* 3. 如果该链表成环,那么fast和slow一定在环中的某个位置相遇
* 4. 相遇后,立刻让fast回到head头结点,slow不动,fast走两步改为每次走一步。fast和slow共同向下滑动,再次相遇,就是成环节点
*
* @param head
* @return
*/
public Node getLoopNode(Node head) {
// 节点数目不足以成环,返回不存在成环节点
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node n1 = head.next; // slow指针
Node n2 = head.next.next; // fast指针
while (n1 != n2) {
// 快指针提前到达终点,该链表无环
if (n2.next == null || n2.next.next == null) {
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
// 确定成环,n2回到头节点
n2 = head;
while (n1 != n2) {
n2 = n2.next;
n1 = n1.next;
}
// 再次相遇节点,就是成环节点
return n1;
}
/**
* 由于单链表,两个链表相交要不然两个无环链表相交,最后是公共部分;要不然两个链表相交,最后是成环部分
* <p>
* 8、判断两个无环链表是否相交,相交则返回相交的第一个节点
* <p>
* 思路:
* 1. 链表1从头结点遍历,统计长度,和最后节点end1
* 2. 链表2从头结点遍历,统计长度,和最后节点end2
* 3. 如果end1不等一end2则一定不相交,如果相等则相交,算长度差,长的链表遍历到长度差的长度位置,两个链表就汇合在该位置
*
* @param head1
* @param head2
* @return
*/
public Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
// 最终没汇聚,说明两个链表不相交
if(cur1 != cur2) {
return null;
}
cur1 = n > 0 ? cur1 : cur2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
/**
* 9、合并两个有序链表
* @param head1
* @param head2
* @return
*/
public Node mergeTwoList(Node head1, Node head2) {
// base case
if (head1 == null || head2 == null) {
return head1 == null ? head2 : head1;
}
// 选出两个链表较小的头作为整个合并后的头结点
Node head = head1.value <= head2.value ? head1 : head2;
// 链表1的准备合并的节点,就是头结点的下一个节点
Node cur1 = head.next;
// 链表2的准备合并的节点,就是另一个链表的头结点
Node cur2 = head == head1 ? head2 : head1;
// 最终要返回的头结点,预存为head,使用引用拷贝的pre向下移动
Node pre = head;
while (cur1 != null && cur2 != null) {
if (cur1.value <= cur2.value) {
pre.next = cur1;
// 向下滑动
cur1 = cur1.next;
} else {
pre.next = cur2;
// 向下滑动
cur2 = cur2.next;
}
// pre向下滑动
pre = pre.next;
}
// 有一个链表耗尽了,没耗尽的链表直接拼上
pre.next = cur1 != null ? cur1 : cur2;
return head;
}
}
```