很多重复的题参考:【代码随想录】双指针法刷题
单链表:
双链表:
循环链表:
单链表节点的定义:
public class ListNode {
public int val; // 节点的值
public ListNode next; // 指向的下一个节点
public ListNode() {}
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
题目:203. 移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
递归:
public ListNode removeElements(ListNode head, int val) {
if (head == null) return head;
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
迭代:找到要删除节点的前一个节点,进行删除操作
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) return head;
ListNode node = head;
while (node != null && node.next != null) {
if (node.next.val == val) node.next = node.next.next;
else node = node.next;
}
return head.val == val ? head.next : head;
}
}
题目:707. 设计链表
动态单链表:(虚拟头节点)
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) {
this.val = val;
}
}
class MyLinkedList {
int size; // 链表元素个数
ListNode head; // 虚拟头节点
// 初始化链表
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
// 获取第 index 个元素的值
public int get(int index) {
if (index < 0 || index >= size) return -1;
ListNode cur = head; // 从头节点开始查找
// 包含虚拟头节点, 所以查找第 index+1 个节点
while (index-- >= 0) cur = cur.next;
return cur.val;
}
// 在链表最前面插入一个节点
public void addAtHead(int val) {
addAtIndex(0, val);
}
// 在链表的最后插入一个节点
public void addAtTail(int val) {
addAtIndex(size, val);
}
// 在第 index 个节点之前插入一个新节点
// 如果 index 为 0,那么新插入的节点为链表的新头节点
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if (index > size) return;
if (index < 0) index = 0;
size++;
// 找到要插入节点的前驱
ListNode pre = head;
while (index-- > 0) pre = pre.next;
// 插入操作
ListNode newNode = new ListNode(val);
newNode.next = pre.next;
pre.next = newNode;
}
// 删除第 index 个节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) return;
size--;
// 找到要删除节点的前驱
ListNode pre = head;
while (index-- > 0) pre = pre.next;
// 删除操作
pre.next = pre.next.next;
}
}
动态双向链表:(虚拟头节点和虚拟尾节点)
class ListNode {
int val;
ListNode next, prev;
ListNode(int x) {
val = x;
}
}
class MyLinkedList {
int size;
ListNode head, tail;
public MyLinkedList() {
size = 0;
head = new ListNode(0); // 虚拟头节点
tail = new ListNode(0); // 虚拟尾节点
head.next = tail;
tail.prev = head;
}
public int get(int index) {
if (index < 0 || index >= size) return -1;
ListNode cur = head;
// 索引 < 一半从前往后找, 索引 > 一半从后前找
if (index < size / 2) {
for (int i = 0; i <= index; i++) cur = cur.next;
} else {
cur = tail;
for(int i = 0; i <= size - 1 - index; i++) cur = cur.prev;
}
return cur.val;
}
public void addAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head.next;
head.next.prev = newNode;
head.next = newNode;
newNode.prev = head;
size++;
// addAtIndex(0, val);
}
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
newNode.next = tail;
newNode.prev = tail.prev;
tail.prev.next = newNode;
tail.prev = newNode;
size++;
// addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index > size) return;
if (index < 0) index = 0;
// 找到要添加的前驱
ListNode pre = head;
while (index-- > 0) pre = pre.next;
// 添加操作
ListNode newNode = new ListNode(val);
newNode.next = pre.next; // 新节点的 next 指向
newNode.prev = pre; // 新节点的 prev 指向
pre.next.prev = newNode; // 后继的 prev 指向新
pre.next = newNode; // 前驱的 next 指向新
size++;
}
public void deleteAtIndex(int index) {
if(index >= size || index < 0) return;
// 找到要删除的前驱
ListNode pre = head;
while (index-- > 0) pre = pre.next;
// 删除操作
pre.next.next.prev = pre;
pre.next = pre.next.next;
size--;
}
}
静态单链表:(虚拟头节点)
class MyLinkedList {
final int N = 1010;
int[] e = new int[N];
int[] ne = new int[N];
int h = 0; // 头节点
int idx = 1; // 有虚拟头节点
int size = 0; // 长度
public MyLinkedList() {}
public int get(int k) {
if (k < 0 || k >= size) return -1;
int t = ne[h];
while (k -- > 0) t = ne[t];
return e[t];
}
public void addAtHead(int val) {
e[idx] = val;
ne[idx] = ne[h];
ne[h] = idx++;
size++;
}
public void addAtTail(int val) {
int t = h;
while (ne[t] != 0) t = ne[t];
e[idx] = val;
ne[idx] = ne[t];
ne[t] = idx++;
size++;
}
public void addAtIndex(int k, int val) {
if (k <= 0) addAtHead(val);
else if (k == size) addAtTail(val);
else if (k > 0 && k < size) {
k --;
int t = ne[h];
while (k -- > 0) t = ne[t];
e[idx] = val;
ne[idx] = ne[t];
ne[t] = idx++;
size++;
}
}
public void deleteAtIndex(int k) {
if (k < 0 || k >= size) return;
int t = h;
while (k -- > 0) t = ne[t];
ne[t] = ne[ne[t]];
size--;
}
}
题目:206. 反转链表
双指针:
public ListNode reverseList(ListNode head) {
ListNode cur = head, pre = null;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
递归:
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode node = reverseList(head.next);
head.next.next = head;
head.next = null;
return node;
}
头插法:空间 O(n)
public ListNode reverseList(ListNode head) {
ListNode res = null;
for (ListNode x = head; x != null; x = x.next)
res = new ListNode(x.val, res);
return res;
}
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
输入:head = [1,2,3,4]
输出:[2,1,4,3]
迭代 + 虚拟头节点:
public ListNode swapPairs(ListNode head) {
ListNode vn = new ListNode(0, head); // 虚拟头节点
ListNode cur = vn, tmp = null;
while (cur != null && cur.next != null && cur.next.next != null) {
tmp = cur.next;
cur.next = cur.next.next;
tmp.next = cur.next.next;
cur.next.next = tmp;
cur = cur.next.next;
}
return vn.next;
}
递归:
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) return head;
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
return next;
}
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
快慢指针:
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null || head.next == null) return null;
ListNode fast = head, slow = head;
while (n-- > 0) fast = fast.next; // 快指针先走 n 步
if (fast == null) return head.next; // 删除第一个节点
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
快慢指针 + 虚拟头节点:
一般使用虚拟头节点可以不用处理特殊情况
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode vn = new ListNode(0, head); // 虚拟头节点
ListNode slow = vn, fast = vn;
while (n-- > 0) fast = fast.next; // 快指针先走 n 步
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return vn.next;
}
递归:
class Solution {
int cur = 0;
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null) return null;
head.next = removeNthFromEnd(head.next, n);
cur++;
if (cur == n) return head.next;
return head;
}
}
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
双指针:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA, curB = headB;
int lenA = 0, lenB = 0;
// 求链表 A 的长度
while (curA != null) {
lenA++;
curA = curA.next;
}
// 求链表 B 的长度
while (curB != null) {
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 链表 A 更长则让 A 多走, B 更长则让 B 多走,保证 A B 从同一位置开始比较
if (lenA > lenB) for (int i = 0; i < lenA - lenB; i++) curA = curA.next;
else for (int i = 0; i < lenB - lenA; i++) curB = curB.next;
// 逐个比较 A 和 B
while (curA != null) {
if (curA == curB) return curA;
curA = curA.next;
curB = curB.next;
}
return curA;
}
巧妙做法:
null
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode h1 = headA, h2 = headB;
while (h1 != h2) {
h1 = (h1 == null) ? headB : h1.next;
h2 = (h2 == null) ? headA : h2.next;
}
return h1;
}
哈希:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>();
ListNode p = headA;
while (p != null) {
set.add(p);
p = p.next;
}
p = headB;
while (p != null) {
if (set.contains(p)) return p;
p = p.next;
}
return null;
}
给你一个链表的头节点 head
,判断链表中是否有环。
快慢指针:
public boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
哈希:
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while (head != null && !set.contains(head)) {
set.add(head);
head = head.next;
}
return head != null;
}
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
快慢指针 判断环 + 找环入口:
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
// 快慢指针判断是否有环
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) { // 有环, 找环的起始点
fast = head; // 快指针从头开始
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
return null;
}
哈希:
public ListNode detectCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while (head != null && !set.contains(head)) {
set.add(head);
head = head.next;
}
return head;
}