一、反转单向链表
private static void reverseNode(Node head) {
Node pre = null;
Node currentNode = head;
while (currentNode != null) {
Node next = currentNode.next;
currentNode.next = pre;
pre = currentNode;
currentNode = next;
}
二、反转双向链表
private static void reverseNode(Node head) {
Node currentNode = head;
Node temp = null;
while (currentNode != null) {
temp = currentNode.next;
currentNode.next = currentNode.pre;
currentNode.pre = temp;
currentNode = temp;
}
}
三、判断一个链表是否为回文结构
[题目]给定一个单链表的头节点head,请判断该链表是否为回文结返回true;
[例子] 1->2->1,返回true;
1->2->2->1,返回true;
15->6->15,返回true;
1->2->3,返回false。
[要求]如果链表长度为N,时间复杂度达到0(N),额外空间复杂度大到0(1)
3.1 使用 Stack来解决(空间复杂度 N)
/**
* 使用栈来解决,空间复杂度是 N
*/
private static boolean checkhuiwen(Node head) {
if (head == null) {
return false;
}
Stack<Node> stack = new Stack<>();
Node p1 = head;
while (p1 != null) {
stack.push(p1);
p1 = p1.next;
}
Node p2 = head;
while (!stack.isEmpty()) {
if (stack.pop().value != p2.value) {
return false;
}
p2 = p2.next;
}
return true;
}
3.2 使用快慢指针来解决(空间复杂度 1)
切成两边,然后从中间往两边进行比对,比对后,再给反转回来就行了~
/**
* 使用指针来解决,空间复杂度是 1
*/
private static boolean checkhuiwen2(Node head) {
if (head == null || head.next == null) {
return true; // 空链表或者只有一个节点时,认为是回文结构
}
Node fastP = head;
Node slowP = head;
while (fastP.next != null && fastP.next.next != null) {
slowP = slowP.next;
fastP = fastP.next.next;
}
Node p1 = head;
Node pre = null;
Node next = null;
while (true) {
next = p1.next;
p1.next = pre;
if(p1 == slowP){
break;
}
pre = p1;
p1 = next;
}
Node pleft = slowP; // 前半部分反转后的头节点
Node pRight = next;
if(fastP.next == null){ // 链表长度奇数个
pleft = pleft.next;
}
boolean flag = true;
while (pleft != null && pRight != null) {
if(pleft.value != pRight.value){
flag = false;
break;
}
pleft = pleft.next;
pRight = pRight.next;
}
// 数组恢复原状
head = reverseLink(slowP);
slowP.next = next;
return flag;
}
// <-1 2 -> 3 -> 4
public static Node reverseLink(Node head){
Node pre = null;
Node next = null;
Node p1 = head;
while(p1 != null){
next = p1.next;
p1.next = pre;
pre = p1;
p1 = next;
}
return pre; // 返回新的头结点
}
四、将单向链表切分左中右
将单向链表按某值划分成左边小、中间相等、右边大的形式
(其实跟快速排序有点像,但是快排的空间复杂度是 NlogN,时间复杂度是 NlogN,并且快排是不稳定的)
[题目] 给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。
实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。
[要求]稳定性
[要求] 时间复杂度请达到0(N),额外空间复杂度请达到0(1)。
public static class Node {
private int value;
private int index;
private Node next;
}
public static void main(String[] args) {
Node aNode = new Node();
aNode.index = 0;
aNode.value = 1;
Node bNode = new Node();
bNode.index = 1;
bNode.value = 3;
Node cNode = new Node();
cNode.index = 2;
cNode.value = 1;
Node dNode = new Node();
dNode.index = 3;
dNode.value = 5;
Node eNode = new Node();
eNode.index = 4;
eNode.value = 2;
Node fNode = new Node();
fNode.index = 5;
fNode.value = 10;
Node gNode = new Node();
gNode.index = 6;
gNode.value = 2;
Node hNode = new Node();
hNode.index = 7;
hNode.value = 3;
aNode.next = bNode;
bNode.next = cNode;
cNode.next = dNode;
dNode.next = eNode;
eNode.next = fNode;
fNode.next = gNode;
gNode.next = hNode;
splitPartition(aNode ,3);
}
private static void splitPartition(Node head,int target) {
Node smallHead = null;
Node smallEnd = null;
Node equalsHead = null;
Node equalsEnd = null;
Node biggerHead = null;
Node biggerEnd = null;
Node p = head;
while(p != null){
if(p.value < target){
if(null != smallEnd){
smallEnd.next = p;
}
smallEnd = p;
if(null == smallHead){
smallHead = p;
}
}
else if(p.value == target){
if(null != equalsEnd){
equalsEnd.next = p;
}
equalsEnd = p;
if(null == equalsHead){
equalsHead = p;
}
} else {
if(null != biggerEnd){
biggerEnd.next = p;
}
biggerEnd = p;
if(null == biggerHead){
biggerHead = p;
}
}
p = p.next;
}
// 极端情况下, target 都比链表中的值小,那么 biggerHead 和 biggerEnd 都会指向 null
if(null != biggerEnd){
biggerEnd.next = null;
}
// 极端情况下, target 都比链表中的值大,那么 smallHead 和 smallEnd 都会指向 null
if(null != smallEnd){
smallEnd.next = equalsHead;
}
// 极端情况下, target 跟链表中对比,没有相等的,那么 equalsHead 和 equalsEnd 都会指向 null
if(null != equalsEnd){
equalsEnd.next = biggerHead;
}
}
五、复制含有随机指针节点的链表
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。
给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
[要求] 时间复杂度0(N),额外空间复杂度0(1)
期望结果如下:
5.1 使用Map去复制
/**
* 使用Map数组映射法
* 空间复杂度是 N
* 时间复杂度是 N
*/
private static Node copyLinkUseMap(Node head) {
Node p1 = head;
Map<Node, Node> map = new HashMap<>();
// 复制节点并建立原节点和复制节点的映射
while (p1 != null) {
Node temp = new Node();
temp.value = p1.value;
temp.index = p1.index;
map.put(p1, temp);
p1 = p1.next;
}
p1 = head;
// 链接复制节点的 next 和 random 指针
while (p1 != null) {
Node temp = map.get(p1);
if(p1.next != null){
temp.next = map.get(p1.next);
}
if(p1.random != null){
temp.random = map.get(p1.random);
}
p1 = p1.next;
}
return map.get(head);// 返回复制链表的头节点
}
5.2 链表后面接着新节点
思路如下,
1、先全部连起来(新节点跟在老节点后面)
2、先处理 random
3、再处理 next
整体流程看图:
/**
* 使用跟屁虫方法
* 空间复杂度是 1
* 时间复杂度是 N
*/
private static Node copyLinkUseAfterNode2(Node head) {
Node p1 = head;
while (p1 != null) {
Node temp = new Node();
temp.index = p1.index + 10;
temp.value = p1.value;
Node next = p1.next;
p1.next = temp;
temp.next = next;
p1 = next;
}
p1 = head;
int count = 1;
Node prev = null;
// 复制 random
while (p1 != null) {
if (count % 2 == 0 && prev != null && prev.random != null) {
p1.random = prev.random.next;
}
prev = p1;
count++;
p1 = p1.next;
}
p1 = head;
Node newHead = p1.next;
Node p2 = head.next != null ? head.next.next : null;
// 连接复制节点和原始节点
while (p1 != null && p2 != null) {
if (p2 != null) {
p1.next.next = p2.next;
}
p1.next = p2;
p1 = p2;
if (p2 != null && p2.next != null) {
p2 = p2.next.next;
}
}
return newHead;
}
六、判断单向链表有没有环
public static class Node {
private int index;
private Node next;
}
public static void main(String[] args) {
Node node0 = new Node();
node0.index = 0;
Node node1 = new Node();
node1.index = 1;
Node node2 = new Node();
node2.index = 2;
Node node3 = new Node();
node3.index = 2;
Node node4 = new Node();
node4.index = 2;
node0.next = node1;
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node1;
// 0 -> 1 -> 2 -> 3 -> 4 -> 1
// boolean containsLoop = checkLoopSet(node0);
boolean containsLoop = checkLoopFS(node0);
System.out.println(containsLoop);
}
/**
* 使用快慢指针,也就是龟兔赛跑,乌龟一次走1步,兔子一次走2步,如果相遇,说明有环
* 空间复杂度是 1
*/
private static boolean checkLoopFS(Node head) {
Node fast = head;
Node slow = head;
// 极端情况 1 -> 2 -> 1
// 如果 fast指针走完了,说明没环
while (fast != null && fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
}
/**
* 使用 Set, 空间复杂度是 N
*/
private static boolean checkLoopSet(Node head) {
Set<Node> set = new HashSet<>();
Node p1 = head;
// 如果没有环,那么最后的节点肯定是 null
while (p1 != null) {
if (set.contains(p1)) {
return true;
}
set.add(p1);
p1 = p1.next;
}
return false;
}
七、单链表有环,找到入环点
/**
* 核心代码
* @param head 头结点
* @param hitNode 相遇点点
* @return
*/
private static Node getLoopFirstNode(Node head ,Node hitNode) {
Node p1 = head;
Node p2 = hitNode;
while(p1 != p2){
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
public static class Node {
private int index;
private Node next;
}
public static void main(String[] args) {
Node node0 = new Node();
node0.index = 0;
Node node1 = new Node();
node1.index = 1;
Node node2 = new Node();
node2.index = 2;
Node node3 = new Node();
node3.index = 3;
Node node4 = new Node();
node4.index = 4;
node0.next = node1;
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node2;
// 0 -> 1 -> 2 -> 3 -> 4 -> 1
// Node hitLoopNode = checkLoopSet(node0);
// 相遇的环形节点
Node hitLoopNode = checkLoopFS(node0);
if (null != hitLoopNode) {
System.out.println("hitLoopNode -> " + hitLoopNode.index);
} else {
System.out.println("No");
}
Node loopFirstNode = null;
// 如果无环
if(null != hitLoopNode){
loopFirstNode = getLoopFirstNode(node0, hitLoopNode);
System.out.println("loopFirstNode -> " + loopFirstNode.index);
} else {
// 走无环的解法
}
}
/**
* @param head 头结点
* @param hitNode 相遇点点
* @return
*/
private static Node getLoopFirstNode(Node head ,Node hitNode) {
Node p1 = head;
Node p2 = hitNode;
while(p1 != p2){
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
/**
* 使用快慢指针,也就是龟兔赛跑,乌龟一次走1步,兔子一次走2步,如果相遇,说明有环
* 空间复杂度是 1
*/
private static Node checkLoopFS(Node head) {
Node slow = head;
Node fast = head;
// 极端情况 1 -> 2 -> 1
// 如果 fast指针走完了,说明没环
while (fast != null && fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
if (fast == slow) {
return fast;
}
}
return null;
}
八、 两个单链表相交问题
[题目] 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返nul
[要求]如果两个链表长度之和为N,时间复杂度请达到0(N),额外空间复杂度请达到0(1)。
8.0 全部情况分析
8.1 链表无环
public static class Node {
private int index;
private Node next;
}
public static void main(String[] args) {
Node aNode = new Node();
aNode.index = 11;
Node bNode = new Node();
bNode.index = 12;
Node cNode = new Node();
cNode.index = 13;
aNode.next = bNode;
bNode.next = cNode;
// a -> b -> c
Node aaNode = new Node();
aaNode.index = 21;
Node bbNode = new Node();
bbNode.index = 22;
aaNode.next = bbNode;
bbNode.next = bNode;
// aa -> bb -> b
/*
a-> b -> c
↗
aa -> bb
*/
Node hitNode = getFirstHitNodeNoLoop(aNode, aaNode);
System.out.println(hitNode);
if(null != hitNode){
System.out.println(hitNode.index);
}
}
private static Node getFirstHitNodeNoLoop(Node headA, Node headB) {
// 如果有一个链表为空,直接返回 null
if (headA == null || headB == null) {
return null;
}
// 1. 先算出俩数组长度,为什么是1呢,因为下面的退出条件是 p1.next == null,也就是会少算一个
int lenA = 1;
int lenB = 1;
Node p1 = headA;
Node p2 = headB;
while (p1.next != null) {
lenA++;
p1 = p1.next;
}
while (p2.next != null) {
lenB++;
p2 = p2.next;
}
// 如果最后的节点不是同一个,那么肯定不相交
if (p1 != p2) {
return null;
}
p1 = headA;
p2 = headB;
int diff = lenB - lenA > 0 ? lenB - lenA : lenA - lenB;
while (diff > 0) {
if (lenA > lenB) {
p1 = p1.next;
} else {
p2 = p2.next;
}
diff--;
}
// 同时移动指针,找到相交节点
while (p1 != null && p2 != null) {
if (p1 == p2) {
return p1;
}
p1 = p1.next;
p2 = p2.next;
}
return null;
}
7.2 整体解法
public static class Node {
private int index;
private Node next;
}
public static void main(String[] args) {
Node a1Node = new Node();
a1Node.index = 11;
Node a2Node = new Node();
a2Node.index = 12;
Node a3Node = new Node();
a3Node.index = 13;
a1Node.next = a2Node;
a2Node.next = a3Node;
a3Node.next = a2Node;
// 11 -> 12 -> 13 -> 12
Node b1Node = new Node();
b1Node.index = 21;
Node b2Node = new Node();
b2Node.index = 22;
b1Node.next = b2Node;
b2Node.next = a2Node;
/*
11-> 12 -> 13
↗
21 -> 22
*/
// 找到a的入环节点,null表示没有环
Node aLoopHitNode = getLoopHitNode(a1Node);
Node bLoopHitNode = getLoopHitNode(b1Node);
System.out.println("aLoopHitNode -> " + aLoopHitNode);
if (null != aLoopHitNode) {
System.out.println("aLoopHitNode -> " + aLoopHitNode.index);
}
System.out.println("bLoopHitNode -> " + bLoopHitNode);
if (null != bLoopHitNode) {
System.out.println("bLoopHitNode -> " + bLoopHitNode.index);
}
Node sameHitNode = null;
// 情况一:a链表和b链表都无环,可能相交,也可能不相交
if (null == bLoopHitNode && null == aLoopHitNode) {
sameHitNode = getSameHitNodeNoLoopWithEnd(a1Node, null, b1Node, null);
}
// 情况二:2.1 俩链表一个有环,一个没环,肯定不相交
if (null != bLoopHitNode && null == aLoopHitNode) {
sameHitNode = null;
}
// 情况二:2.2 俩链表一个有环,一个没环,肯定不相交
if (null == bLoopHitNode && null != aLoopHitNode) {
sameHitNode = null;
}
// 情况三:俩链表都有环
if (null != bLoopHitNode && null != aLoopHitNode) {
// 3.1 碰撞点是同一个,说明在环前面(或者刚好环的入口)
if (aLoopHitNode == bLoopHitNode) {
// 为什么是 aLoopHitNode.next 与 bLoopHitNode.next?
// 因为如果两个链表的相交点,刚好是入环点的话,那么他们终止节点应该往后挪一个,进入这个方法后,才会使得 p1 = p2
sameHitNode = getSameHitNodeNoLoopWithEnd(a1Node, aLoopHitNode.next, b1Node, bLoopHitNode.next);
} else {
// 判断是否共享同一个环
Node p1 = aLoopHitNode.next;
while (p1 != aLoopHitNode) {
if (p1 == bLoopHitNode) { // 碰上了,肯定是同一个环,一定有交点
sameHitNode = aLoopHitNode;
break;
}
p1 = p1.next;
}
// 3.2 如果a绕了一圈,没找到b,说明不是一个环
if(p1 == aLoopHitNode){
sameHitNode = null;
}
}
}
System.out.println("sameHitNode -> " + sameHitNode);
if (null != sameHitNode) {
System.out.println(sameHitNode.index);
}
}
/**
* 2个无环链表求交点
*
* @return 返回null表示没交点
*/
private static Node getSameHitNodeNoLoopWithEnd(Node aHead, Node aEnd, Node bHead, Node bEnd) {
if (aHead == aEnd || bHead == bEnd) {
return null;
}
int aLen = 1;
int bLen = 1;
Node p1 = aHead;
Node p2 = bHead;
while (p1.next != aEnd) {
aLen++;
p1 = p1.next;
}
while (p2.next != bEnd) {
bLen++;
p2 = p2.next;
}
// 情况1: 终止节点不是一个,那肯定不相交
if (p1 != p2) {
return null;
}
// 情况2:终止节点是一个,需要找到相交点
p1 = aHead;
p2 = bHead;
// 看看俩链表相差多少
int diff = Math.abs(aLen - bLen);
while (diff > 0) {
if (aLen > bLen) {
p1 = p1.next;
} else {
p2 = p2.next;
}
diff--;
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
/**
* 快慢指针拿到入环节点
*/
private static Node getLoopHitNode(Node head) {
Node fast = head;
Node slow = head;
if (null == head) {
return null;
}
if (fast.next == null || fast.next.next == null) {
return null;
}
// 看下是否能碰撞上
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
// 说明没环,退出是因为 fast.next == null
if (fast != slow) {
return null;
}
// 有环的情况下,slow指向 head,然后slow和fast每次走一步
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}