1 哨兵节点
思路:在单链表中移除或添加头结点 和 其他节点的操作方式是不一样,其实在写代码的时候也会发现,需要单独写一段逻辑来处理头结点的情况。遇到需要移除和添加节点时,可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除和添加了。
// 203. 移除链表元素
// 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(0,head);
ListNode cur = dummy;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return dummy.next;
}
}
2 链表常规操作
思路:模拟链表的增删改查操作
// 707. 设计链表
// 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。在链表类中实现这些功能:get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
class MyLinkedList {
private int size;
private ListNode head;
public static class ListNode {
int val;
ListNode next;
public ListNode (int val) {
this.val = val;
}
public ListNode (int val,ListNode next) {
this.val = val;
this.next = next;
}
}
/** Initialize your data structure here. */
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
public int get(int index) {
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = head;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
public void addAtHead(int val) {
addAtIndex(0,val);
}
/** Append a node of value val to the last element of the linked list. */
public void addAtTail(int val) {
addAtIndex(size,val);
}
/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
public void addAtIndex(int index, int val) {
if (index > size) return;
if (index < 0) index = 0;
size++;
ListNode tmpNode = new ListNode(val);
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
tmpNode.next = pre.next;
pre.next = tmpNode;
}
/** Delete the index-th node in the linked list, if the index is valid. */
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) return;
size--;
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
}
}
3 双指针
3.1 反转链表
思路:使用双指针分别指向链表相邻的两个位置,进行指针交换反转链表。
// 206. 反转链表
// 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
ListNode tmp = null;
while (cur != null) {
tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
// 剑指 Offer II 025. 链表中的两数相加
// 给定两个 非空链表 l1和 l2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。可以假设除了数字 0 之外,这两个数字都不会以零开头。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head1 = reverseList(l1);
ListNode head2 = reverseList(l2);
ListNode sumHead = addReversedList(head1,head2);
return reverseList(sumHead);
}
private ListNode reverseList(ListNode root) {
ListNode cur = root;
ListNode pre = null;
ListNode tmp = null;
while (cur != null) {
tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
private ListNode addReversedList(ListNode head1,ListNode head2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
int carry = 0;
while (head1 != null || head2 != null) {
int sum = (head1 == null ? 0 : head1.val) + (head2 == null ? 0 : head2.val) + carry;
carry = sum >= 10 ? 1 : 0;
sum = sum >= 10 ? sum - 10 : sum;
ListNode tmpNode = new ListNode(sum);
cur.next = tmpNode;
cur = cur.next;
head1 = head1 == null ? null : head1.next;
head2 = head2 == null ? null : head2.next;
}
if (carry > 0) {
cur.next = new ListNode(carry);
}
return dummy.next;
}
}
// 剑指 Offer II 026. 重排链表
// 给定一个单链表 L 的头节点 head ,单链表 L 表示为: L0 → L1 → … → Ln-1 → Ln 请将其重新排列后变为:L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
class Solution {
public void reorderList(ListNode head) {
ListNode dummy = new ListNode(0,head);
ListNode slow = dummy;
ListNode fast = dummy;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode tmp = slow.next;
slow.next = null;
link(head,reverseList(tmp),dummy);
}
private ListNode reverseList(ListNode root) {
ListNode cur = root;
ListNode pre = null;
ListNode tmp = null;
while (cur != null) {
tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
private void link(ListNode node1,ListNode node2,ListNode head) {
while (node1 != null && node2 != null) {
ListNode tmp = node1.next;
head.next = node1;
node1.next = node2;
head = node2;
node1 = tmp;
node2 = node2.next;
}
if (node1 != null) {
head.next = node1;
}
}
}
// 剑指 Offer II 027. 回文链表
// 给定一个链表的 头节点 head ,请判断其是否为回文链表。如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode dummy = new ListNode(0,head);
ListNode fast = dummy;
ListNode slow = dummy;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode tmp = slow.next;
slow.next = null;
return isPalindrome(head,reverseList(tmp));
}
private ListNode reverseList(ListNode root) {
ListNode cur = root;
ListNode pre = null;
ListNode tmp = null;
while (cur != null) {
tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
private boolean isPalindrome(ListNode node1,ListNode node2) {
while (node1 != null && node2 != null) {
if (node1.val != node2.val) return false;
node1 = node1.next;
node2 = node2.next;
}
return true;
}
}
3.2 快慢指针
思路:采用快慢指针,让快指针先移动到指定位置后,慢指针再做移动处理。
// 19. 删除链表的倒数第 N 个结点
// 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0,head);
ListNode slow = dummy;
ListNode fast = dummy;
while (n-- > 0) {
fast = fast.next;
}
fast = fast.next; //多走一步,让slow少走一步指向pre节点
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
// 142. 环形链表 II
// 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
ListNode node1 = head;
ListNode node2 = fast;
while (node1 != node2) {
node1 = node1.next;
node2 = node2.next;
}
return node1;
}
}
return null;
}
}
3.3 指向不同链表指针
思路:根据需求分别移动指针的位置,从而满足题目要求。
// 面试题 02.07. 链表相交
// 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
while (curA != null) {
curA = curA.next;
lenA++;
}
while (curB != null) {
curB = curB.next;
lenB++;
}
curA = headA;
curB = headB;
if (lenA < lenB) {
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
int gap = lenA - lenB;
while (gap-- > 0) {
curA = curA.next;
}
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
4 双向链表
思路:双向链表在单向链表的基础上增加了指向前一个节点的指针,这样既可以从头节点开始从前往后遍历,也可以从后往前遍历。
// 剑指 Offer II 028. 展平多级双向链表
// 多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。
class Solution {
public Node flatten(Node head) {
flatGetTail(head);
return head;
}
private Node flatGetTail(Node head) {
Node node = head;
Node tail = null;
while (node != null) {
Node next = node.next;
if (node.child != null) {
Node child = node.child;
Node childTail = flatGetTail(child);
node.child = null;
node.next = child;
child.prev = node;
childTail.next = next;
if (next != null) {
next.prev = childTail;
}
tail = childTail;
} else {
tail = node;
}
node = next;
}
return tail;
}
}
5 循环链表
思路:把链表的尾节点指向的下一个节点的指针指向链表的头节点。此时链表就变成了一个循环链表。
// 剑指 Offer II 029. 排序的循环链表
// 给定循环单调非递减列表中的一个点,写一个函数向这个列表中插入一个新元素 insertVal ,使这个列表仍然是循环升序的。给定的可以是这个列表中任意一个顶点的指针,并不一定是这个列表中最小元素的指针。如果有多个满足条件的插入位置,可以选择任意一个位置插入新的值,插入后整个列表仍然保持有序。如果列表为空(给定的节点是 null),需要创建一个循环有序列表并返回这个节点。否则。请返回原先给定的节点。
class Solution {
public Node insert(Node head, int insertVal) {
Node node = new Node(insertVal);
if (head == null) {
head = node;
head.next = head;
} else if (head == head.next) {
node.next = head;
head.next = node;
} else {
insertCore(head,node);
}
return head;
}
private void insertCore(Node head,Node node) {
Node cur = head;
Node next = head.next;
Node biggest = head;
while (!(cur.val <= node.val && node.val <= next.val) && next != head) {
cur = next;
next = next.next;
if (cur.val >= biggest.val) {
biggest = cur;
}
}
if (cur.val <= node.val && node.val <= next.val) {
node.next = next;
cur.next = node;
} else {
node.next = biggest.next;
biggest.next = node;
}
}
}