P5 链表
哈希表和有序表
| - | 数据结构 | 操作时间复杂度 |
|---|---|---|
| 哈希表 | HashSet、HashMap | O(1) |
| 有序表 | TreeSet、TreeMap | O(logN) |
单向链表和双向链表
// 单向链表
Class Node{
int v;
Node next;
}
// 双向链表
Class Node2{
int v;
Node2 pre;
Node2 next;
}
反转单向链表
public Node reserveList(Node node) {
if(node == null || node.next == null) {
return node;
}
Node pre = null;
Node current = node;
Node next = null;
while(current != null) {
next = current.next;
current.next = pre;
pre = current;
current = next;
}
return pre;
}
打印两个有序链表的公共部分
public void printCommon(Node n1, Node n2) {
Node p1 = n1;
Node p2 = n2;
while (p1 != null && p2 != null) {
if(p1.v == p2.v) {
System.out.println(p1.v);
p1 = p1.next;
p2 = p2.next;
} else if(p1.v < p2.v) {
p1 = p1.next;
} else {
p2 = p2.next;
}
}
}
链表解题技巧
快慢指针和额外数据结构
链表笔试技巧
尽可能优化时间复杂度,空间复杂度过了就行
链表面试技巧
保证时间复杂度,尽可能优化空间复杂度
判断一个链表是否是回文
可以实现的方案:
- 把链表全部压栈,然后出栈比较
- 快慢指针,等到快指针到链表结束的时候,慢指针开始压栈,然后出栈比较
- 快慢指针,然后慢指针修改链表方向,再从两侧开始往中间比较,比较的过程中还原链表
// 方案1
public boolean isPalindrome1(Node node) {
if (node == null || node.next == null) {
return true;
}
Stack<Node> stack = new Stack<>();
Node p = node;
while (p != null) {
stack.push(p);
p = p.next;
}
p = node;
boolean res = true;
while (!stack.isEmpty()) {
if (stack.pop().v != p.v) {
res = false;
break;
}
p = p.next;
}
return res;
}
// 方案2
public boolean isPalindrome2(Node node) {
if (node == null || node.next == null) {
return true;
}
Node fast = node;
Node slow = node;
// 压栈的位置
Node push = null;
while (fast != null) {
if (fast.next == null) {
push = slow;
break;
}
fast = fast.next.next;
slow = slow.next;
}
// 根据链表单双处理一下入栈位置
push = push == null ? slow : push;
Stack<Node> stack = new Stack<>();
while (push != null) {
stack.push(push);
push = push.next;
}
boolean res = true;
while (!stack.isEmpty()) {
if (stack.pop().v != node.v) {
res = false;
break;
}
node = node.next;
}
return res;
}
public boolean isPalindrome3(Node node) {
if (node == null || node.next == null) {
return true;
}
Node fast = node;
Node slow = node;
// 压栈的位置
Node push = null;
// slow 前一个节点
Node pre = null;
while (fast != null) {
// 奇数个节点
if (fast.next == null) {
push = slow.next;
pre = slow;
break;
}
pre = slow;
fast = fast.next.next;
slow = slow.next;
}
// 偶数个节点下设置反转位置
if (push == null) {
push = slow;
}
// 把 push 位置链表反向
Node current = push;
Node next = null;
while (current != null) {
next = current.next;
current.next = pre;
pre = current;
current = next;
}
// 最后一个节点的位置
Node tail = pre;
Node head = node;
// 恢复链表的指针
Node used = null;
Node usedNext = null;
boolean res = true;
// 如果链表有环说明链表没恢复
while (push.next != null && push.next.next == push) {
if (head.v != tail.v) {
res = false;
break;
}
head = head.next;
// 恢复链表
used = tail;
tail = tail.next;
used.next = usedNext;
usedNext = used;
}
if (push.next != null && push.next.next == push) {
used = tail;
tail = tail.next;
head = head.next;
used.next = usedNext;
usedNext = used;
}
return res;
}
把单向链表按照某值划分成左边小、中间相等、右边大的形式
额外要求:
- 保证排序稳定性
- 时间复杂度O(N), 空间复杂度O(1)
可以实现的方案:
- 把 Node 放进数组,做 partition
- 把链表拆分成 3 个链表,再连接起来
// 方案2
public Node sortByK(Node node, int k) {
if (node == null || node.next == null) {
return node;
}
Node lessKHead = null;
Node lessKTail = null;
Node equalKHead = null;
Node equalKTail = null;
Node moreKHead = null;
Node moreKTail = null;
while (node != null) {
if (node.v == k) {
if (equalKHead == null) {
equalKHead = node;
} else {
equalKTail.next = node;
}
equalKTail = node;
} else if (node.v < k) {
if (lessKHead == null) {
lessKHead = node;
} else {
lessKTail.next = node;
}
lessKTail = node;
} else {
if (moreKHead == null) {
moreKHead = node;
} else {
moreKTail.next = node;
}
moreKTail = node;
}
node = node.next;
}
Node head = null;
Node tail = null;
if (lessKHead != null) {
head = lessKHead;
tail = lessKTail;
}
if (equalKHead != null) {
if (head == null) {
head = equalKHead;
} else {
tail.next = equalKHead;
}
tail = equalKTail;
}
if (moreKHead != null) {
if (head == null) {
head = moreKHead;
} else {
tail.next = moreKHead;
}
tail = moreKTail;
}
return head;
}
复制含有随机指针节点的链表
链表结构如下:
class RNode{
int v;
RNode next;
RNode rand;
public RNode(int v, RNode next, RNode rand) {
this.v = v;
this.next = next;
this.rand = rand;
}
}
rand 指针可能指向链表中任意一个节点,也可能指向 null ,给定一个由RNode节点构成的无环单链表 head,实现链表的复制。
额外要求:时间复杂度 O(N),空间复杂度 O(1)
可以实现的方案:
- 哈希表,空间复杂度 O(N)
- 把复制节点先放在原来链表后面,然后完成 rand 指针复制
public RNode copyList(RNode head) {
if (head == null) {
return null;
}
RNode p = head;
while (p != null) {
RNode copy = new RNode(p.v, p.next, null);
p.next = copy;
p = p.next.next;
}
p = head;
while (p != null) {
p.next.rand = (p.rand == null) ? null : p.rand.next;
p = p.next.next;
}
RNode res = head.next;
p = head;
RNode copy = p.next;
while (copy != null) {
p.next = copy.next;
p = p.next;
copy.next = p == null ? null : p.next;
copy = copy.next;
}
return res;
}
两个单链表相交的问题
给定两个单链表(可能有环),头节点是 head1,head2,实现一个函数,如果两个链表相交,返回相交的第一个节点,否则返回 null
额外要求:如果两个链表长度之和是 N,时间复杂度 O(N),空间复杂度 O(1)
解决方案:
- 先检查两个链表,如果两个链表都是无环的,那么遍历两个链表,长度记为 l1 和 l2,较长的链表从头节点开始移动至两个链表剩余长度相同,然后两个链表一边遍历一边比较
- 如果两个链表都是有环的,如果入环节点相同,那么就是相交的,可以把环去除用 1 实现;如果不同,只需要看两个节点是不是再一个环上(如果一个节点在两个换上,那它有两个 next 节点)
- 如果其中一个有环,不相交
// 快慢指针看相遇
// 从 head 再来一个慢指针, 两个慢指针一起移动到相遇节点,就是第一个入环节点
public Node firstRingNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node fast = head.next.next;
Node slow = head.next;
while (fast != slow) {
if (fast.next == null || fast.next.next == null) {
return null;
}
fast = fast.next.next;
slow = slow.next;
}
Node node = head;
while (node != slow) {
node = node.next;
slow = slow.next;
}
return node;
}
public Node firstIntersectNode(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node node1 = firstRingNode(head1);
Node node2 = firstRingNode(head2);
if (node1 == null && node2 == null) {
return noLoop(head1, head2, null, null);
}
if (node1 != null && node2 != null) {
return bothLoop(head1, head2, node1, node2);
}
return null;
}
// 1.处理无环情况
// 2.处理环是公有的情况
public Node noLoop(Node head1, Node head2, Node tail1, Node tail2) {
Node node1 = head1;
Node node2 = head2;
int l1 = 1;
int l2 = 1;
while (node1.next != tail1) {
l1++;
node1 = node1.next;
}
while (node2.next != tail2) {
l2++;
node2 = node2.next;
}
if (node1 != node2) {
return null;
}
node1 = head1;
node2 = head2;
while (l1 > l2) {
node1 = node1.next;
l1--;
}
while (l1 < l2) {
node2 = node2.next;
l2--;
}
while (node1 != node2) {
node1 = node1.next;
node2 = node2.next;
}
return node1;
}
// 两个环的情况
public Node bothLoop(Node head1, Node head2, Node loop1, Node loop2) {
if (loop1 == loop2) {
return noLoop(head1, head2, loop1, loop2);
} else {
Node p = loop1.next;
while (p != loop1) {
if (p == loop2) {
return loop2;
}
}
}
return null;
}