1.反转链表
题目描述:
给定单链表的头节点 head
,请反转链表,并返回反转后的链表的头节点。
(1)迭代解法
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
// 依次迭代反转每个节点
while (cur != null) {
ListNode p = cur.next;
cur.next = pre;
pre = cur;
cur = p;
}
return pre;
}
(2)递归解法
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) {
return head;
}
// 反转head后的所有节点,返回头节点(最后一个节点)
ListNode last = reverseList(head.next);
// 然后反转head节点
head.next.next = head;
head.next = null;
return last;
}
2.环形链表
一般解法:快慢指针,如果快慢指针相遇就说明一定有环。
题目描述:
给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next
指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null
。
算法分析:
解法一:使用哈希表记录节点是否出现过。 空间和时间复杂度均为 O(N)
public ListNode detectCycle(ListNode head) {
ListNode pos = head;
Set<ListNode> visited = new HashSet<ListNode>();
while (pos != null) {
if (visited.contains(pos)) {
return pos;
} else {
visited.add(pos);
}
pos = pos.next;
}
return null;
}
解法二:快慢指针 时间复杂度O(N) 空间复杂度O(1)
如图所示,设链表中环外部分的长度为 a。slow
指针进入环后,又走了 b 的距离与 fast
相遇。此时,fast
指针已经走完了环的 n 圈,因此它走过的总距离为 a+n(b+c)+b = a+(n+1)b+nc。
因为fast
指针走过的距离为 slow
指针的 2 倍。
发现:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。
所以,当快慢指针相遇后,可以再额外使用一个 p 节点将其从链表头部开始移动,slow
指针和 p
指针同时移动,它们将会在入环点相遇。
public ListNode detectCycle(ListNode head) {
if(head == null || head.next ==null) {
return null;
}
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
break;
}
}
if(fast == null || fast.next == null) {
return null;
}
ListNode p = slow;
slow = head;
while(p != slow) {
p = p.next;
slow = slow.next;
}
return p;
}
3.删除节点
快慢指针,需要注意头节点如果被删除后的处理
给定一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode p = head;
for(int i = 1; i < n; ++i) {
if(p == null) {
break;
}
p = p.next;
}
if(p == null) {
return null;
}
ListNode newHead = new ListNode();
newHead.next = head;
ListNode cur = newHead;
while(p.next != null) {
cur = cur.next;
p = p.next;
}
cur.next = cur.next.next;
return newHead.next;
}
4.复杂链表的复制
题目描述:
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
解法一:哈希表
对每个原节点和新节点都用哈希表键值对存储对应关系,然后再遍历原链表,通过映射找到新节点并对其建立关系。
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
Map<Node, Node> map = new HashMap<>();
// 1. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(cur != null) {
map.put(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
// 2. 构建新链表的 next 和 random 指向
while(cur != null) {
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
// 3. 返回新链表的头节点
return map.get(head);
}
解法二:拼接 + 拆分
在每个原节点后都插入一个新节点,构建一个拼接链表,这样就可以很方便的找到新节点random
指向的新节点,即旧节点random
对应的节点的next
。最后再拆分出新建立的链表即可。
拼接链表:原节点 1 -> 新节点 1 -> 原节点 2 -> 新节点 2 -> ……
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
// 1. 复制各节点,并构建拼接链表
while(cur != null) {
Node tmp = new Node(cur.val);
tmp.next = cur.next;
cur.next = tmp;
cur = tmp.next;
}
// 2. 构建各个新节点的 random 指向
cur = head;
while(cur != null) {
if(cur.random != null)
cur.next.random = cur.random.next;
cur = cur.next.next;
}
// 3. 拆分两链表
cur = head.next;
Node pre = head, res = head.next;
while(cur.next != null) {
pre.next = pre.next.next;
cur.next = cur.next.next;
pre = pre.next;
cur = cur.next;
}
pre.next = null; // 单独处理原链表尾节点
return res; // 返回新链表头节点
}