Skip to content

Commit 96ba7bb

Browse files
author
dai
committed
增加专题
1 parent b91eda1 commit 96ba7bb

File tree

3 files changed

+1068
-0
lines changed

3 files changed

+1068
-0
lines changed

25-算法面试专题-链表.md

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

0 commit comments

Comments
 (0)