参考:CS-Notes
1. 相交链表
(1)双指针
/**
* 设 c 为相交的部分
* headA = a + c,headB = b + c
* a + c + b = b + c + a
* 所以访问 a 之后继续访问 b,另一条链表访问 b 之后继续访问 a
* 这样它们就能同时到达交点了
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode l1, l2;
l1 = headA;
l2 = headB;
while (l1 != l2) {
l1 = (l1 != null) ? l1. next : headB;
l2 = (l2 != null) ? l2.next : headA;
}
return l1;
}
}
(2)通过链表长度判断
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
// 1 遍历两个链表找到每个链表的最后一个节点
// 同时计算其链表长度
ListNode tempA = headA;
ListNode tempB = headB;
int countA = 1;
int countB = 1;
while (tempA.next != null) {
countA++;
tempA = tempA.next;
}
while (tempB.next != null) {
countB++;
tempB = tempB.next;
}
// 2 判断两个链表的最后一个节点是否相等
// 不相等说明两个链表必不可能相交
if (tempA != tempB) {
return null;
}
// 3 同时回到链表头部,两个链表长度大的先走
// 走的步数为两个链表长度的差值
tempA = headA;
tempB = headB;
if (countA > countB) {
int count = countA - countB;
while (count-- != 0) {
tempA = tempA.next;
}
}
if (countB > countA) {
int count = countB - countA;
while (count-- != 0) {
tempB = tempB.next;
}
}
// 第一个相交节点就是环的入口
while (tempA != tempB) {
tempA = tempA.next;
tempB = tempB.next;
}
return tempA;
}
}
2. 反转链表
- 递归法
class Solution {
public ListNode reverseList(ListNode head) {
// 结束条件
if (head == null || head.next == null) {
return head;
}
// 思想就是看成两个节点,后一个节点的 next 指向前一个节点
// 前一个节点的 next 指向 null,完成链表反转
ListNode next = head.next;
ListNode newHead = reverseList(next);
next.next = head;
head.next = null;
return newHead;
}
}
- 头插法
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
// 修改指向的节点
ListNode temp = head;
// 保存 temp 下一个节点
ListNode next = null;
// 新链表的头节点
ListNode newHead = null;
// 思想就是先保存下一个节点
// 再把当前节点的 next 指向新链表
// 然后再把新链表的 head 指向当前节点
// 之后再后移一个节点
while (temp != null) {
// 保存下一个节点,避免指向丢失
next = temp.next;
temp.next = newHead;
newHead = temp;
temp = next;
}
return newHead;
}
}
3. 合并两个有序链表
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 考虑特殊情况
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
// 如果 l1 的第一个节点的值小于 l2 第一个节点的值
// 就把 l1 的第一个节点加入到新链表中,同时后移一个节点
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
4. 删除排序链表中的重复元素
(1)暴力
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = head;
ListNode temp = newHead;
head = head.next;
while (head != null) {
if (temp.val != head.val) {
temp.next = head;
temp = temp.next;
}
head = head.next;
}
temp.next = null;
return newHead;
}
}
(2)递归
class Solution {
public ListNode deleteDuplicates(ListNode head) {
// 终止条件
if (head == null || head.next == null) {
return head;
}
head.next = deleteDuplicates(head.next);
return ((head.val) == (head.next.val)) ? head.next : head;
}
}
5. 删除链表的倒数第 N 个结点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 快慢指针
ListNode fast = head;
ListNode slow = head;
while (n-- != 0) {
fast = fast.next;
}
// 说明要删除第一个节点
if (fast == null) {
return head.next;
}
while(fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
}
6.返回倒数第 k 个节点
class Solution {
public int kthToLast(ListNode head, int k) {
ListNode fast = head;
while (k-- != 0) {
fast = fast.next;
}
if (fast == null) {
return head.val;
}
ListNode slow = head;
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow.next.val;
}
}
7. 两两交换链表中的节点
class Solution {
public ListNode swapPairs(ListNode head) {
// 1 结束条件
// 当链表为 null 时或者只剩一个链表时结束
if (head == null || head.next == null) {
return head;
}
// 2 本级递归应该做什么
// 交换两个链表节点的值
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
// 返回值
// 已经交换好了的链表
return next;
}
}
8. 两数相加 II
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// 因为是逆序计算,所以使用栈来计算最为方便
Stack<Integer> l1Stack = build(l1);
Stack<Integer> l2Stack = build(l2);
// 新链表的头指针
ListNode head = new ListNode();
// 要进的位数,例如 9 + 3 = 12,carry = 12 % 10 = 2
int carry = 0;
// 当 l1 和 l2 的栈为空,同时没有要进的位数就跳出循环
// 构造链表
while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
// 栈不为空才取值,为空就给 0,因为是加法
Integer a = l1Stack.isEmpty() ? 0 : l1Stack.pop();
Integer b = l2Stack.isEmpty() ? 0 : l2Stack.pop();
Integer sum = a + b + carry;
carry = sum / 10;
// 构造一个新节点
ListNode node = new ListNode(sum % 10);
// 加入到链表中
node.next = head.next;
head.next = node;
}
return head.next;
}
/**
* 将链表压入栈中
*/
public Stack<Integer> build(ListNode l) {
Stack<Integer> stack = new Stack<>();
while (l != null) {
stack.push(l.val);
l = l.next;
}
return stack;
}
}
9. 回文链表
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
// 快慢指针,走的过程中同时反转
ListNode slow = head;
ListNode fast = head;
ListNode pre = slow;
ListNode prepre = null;
// 如果是奇数个节点,那么 fast != null 就会跳出循环
// 如果是偶数个节点,那么 fast 为 null 时才会跳出循环
while (fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
pre.next = prepre;
prepre = pre;
}
if (fast != null) {
slow = slow.next;
}
// 进行比较
while (pre != null && slow != null) {
if (pre.val != slow.val) {
return false;
}
pre = pre.next;
slow = slow.next;
}
return true;
}
}
10. 分隔链表
class Solution {
public ListNode[] splitListToParts(ListNode head, int k) {
// 计算出链表的长度
int N = 0;
ListNode cur = head;
while (cur != null) {
N++;
cur = cur.next;
}
// 计算出每段的长度以及多余的长度
int size = N / k;
int mod = N % k;
// 多余的长度的处理:分别加在从第一段开始的每一段
// 没有多余就不用加了
// 定义要返回的链表数组
ListNode[] result = new ListNode[k];
cur = head;
for (int i = 0; cur != null && i < k; i++) {
result[i] = cur;
// 找到合适长度,然后截断
int curSize = size + (mod-- > 0 ? 1 : 0);
for (int j = 0; j < curSize - 1; j++) {
cur = cur.next;
}
ListNode next = cur.next;
cur.next = null;
cur = next;
}
return result;
}
}
11. 奇偶链表
class Solution {
public ListNode oddEvenList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 思想就是:把奇偶节点分离出来,组成两段链表
// 最后再把偶链表的头节点连在奇链表的尾节点就行
ListNode old = head;
// 偶链表的头
ListNode evenHead = head.next;
ListNode even = evenHead;
// 进行分离
while (even != null && even.next != null) {
old.next = old.next.next;
old = old.next;
even.next = even.next.next;
even = even.next;
}
// 两段链表的连接
old.next = evenHead;
return head;
}
}
12. 环形链表
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
if (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
}
13. 环形链表 II
(1)使用额外空间
public class Solution {
// 利用哈希表遍历存储,当节点重复了说明就是环的入口
public ListNode detectCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while (head != null) {
if (set.contains(head)) {
return head;
}
set.add(head);
head = head.next;
}
return null;
}
}
(2)不使用额外空间(快慢指针)
public class Solution {
// 使用快慢指针
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
// 1 找到第一次快慢指针相遇的位置
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
break;
}
}
if (fast == null || fast.next == null) {
return null;
}
// 2 快指针指向链表头部,并且一次走一步
fast = head;
// 3 当快慢指针再次相遇的位置即为环的入口
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}