-
链表
-
移除链表元素
-
203.移除链表元素
-
-
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 设置一个前指针一后指针 两两相邻进行移动 当后指针指向结点等于val 的节点时 直接使 前指针指向结点的后继指针 指向 后指针指向结点的后继结点 跳过当前等于val 的节点 即表示删除
// 考虑到头结点可能会是== val 的节点 直接在头结点前面再加一个虚拟头结点 它的后继结点指向头结点 方便循环操作删除结点 结果返回虚拟头结点的后继结点即可
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) { // 判断是否为空链表
return null;
}
ListNode dummy = new ListNode(-1, head); // 新建虚拟头结点
ListNode p = dummy; // 前指针
ListNode q = dummy.next; // 后指针
while (q != null) { // 遍历链表
if (q.val == val) p.next = q.next; // 当后指针结点== val节点时 直接使 前指针指向结点的后继指针 指向 后指针指向结点的后继结点 跳过当前等于val 的节点 即表示删除
else p = q; // 两两相邻进行移动
q = q.next; // 移动后指针
}
return dummy.next; // 结果返回虚拟头结点的后继结点 即真实头结点
}
}
-
-
设计链表
-
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 ListNode { // 设定结点 数据域 指针域
int val;
ListNode next;
ListNode () {}
ListNode (int val) {
this.val = val;
}
}
class MyLinkedList {
int size; // 链表长度
ListNode head; // 链表头结点
public MyLinkedList() { // 初始化链表
size = 0; // 初始化长度为0
head = new ListNode (0); // 建立头结点
}
public int get(int index) { // 查找第index个结点的值
if (index < 0 || index >= size) { // 判断index是否超出链表范围
return -1;
}
ListNode p = head; // 指针p
for (int i = 0; i <= index; ++i) { // 循环遍历 将 指针p 移动至第index个结点上
p = p.next; // 指针p往后移动
}
return p.val; // 返回第index个结点的值
}
public void addAtHead(int val) { // 头插法 调用addAtIndex(0, val);方法
addAtIndex(0, val);
}
public void addAtTail(int val) { // 尾插法 调用addAtIndex(size, val);方法
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) { // 将元素值插入链表指定位置
if (index > size) { // 不能超出链表范围
return;
}
if (index < 0) { // 插入位置小于0则 插入到头结点
index = 0;
}
size++; // 可插入结点 链表长度++
ListNode p = head; // 指针p
for (int i = 0; i < index; ++i) { // 循环遍历 找到要插入位置的前继结点
p = p.next; // 指针p往后移动
}
ListNode m = new ListNode(val); // 新建结点
m.next = p.next; // 将新建结点插入链表
p.next = m;
}
public void deleteAtIndex(int index) { // 删除指定链表index位置上的结点
if (index < 0 || index >= size) { // 不能超出链表范围
return;
}
size--; // 可删除结点 链表长度--
ListNode p = head; // 循环遍历 找到要删除位置的前继结点
for (int i = 0; i < index; ++i) {
p = p.next;
}
p.next = p.next.next; // 移动指针 越过该要删除的结点
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
-
-
反转链表
-
707反转链表
-
-
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 改变链表的next指针的指向,直接将链表反转 一个前指针一个后指针 一个保存指针
// 循环遍历链表 先将后指针结点的后继结点保存 将后指针的指针域指向前指针结点 再前指针指向后指针结点 后指向保存结点 再次移动 遍历完成即反转完成
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null; // 保存指针
ListNode p = head; // 前指针
ListNode q = null; // 后指针
while (p != null) { // 循环遍历链表
pre = p.next; // 将后指针结点的后继结点保存
p.next = q; // 后指针的指针域指向前指针结点
q = p; // 前指针 指向 后指针结点
p = pre; // 后指向保存结点 再次循环遍历移动
}
return q; // 返回前指针 即反转后的头结点
}
}
-
-
两两交换链表的结点
-
24.两两交换链表的结点
-
-
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 虚拟头结点方法
// 建立虚拟头结点 方便交换前两个结点 三个指针 按链表顺序依次指向 · 先交换指针二指针三结点 · 指针一指针域 指向 指针三结点 · 指针二指针域 指向 指针三后继结点 · 指针三指针域 指向 指针二结点 · 前两个结点交换完成 · 指针一再指向之前指针二 即交换后两个结点被换至到后面的结点 · 指针二指针三依次指向指针一后继结点 再一次交换结点 以次循环交换
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyNode = new ListNode(0, head); // 建立虚拟头结点 方便交换前两个结点
ListNode pre = dummyNode; // 指针一
while (pre.next != null && pre.next.next != null) { // 需要交换的两个不能为空
ListNode p = pre.next; // 指针二
ListNode q = pre.next.next; // 指针三
pre.next = q; // 指针一指针域 指向 指针三结点
p.next = q.next; // 指针二指针域 指向 指针三后继结点
q.next = p; // 指针三指针域 指向 指针二结点
pre = p; // 指针一再指向之前的指针二的结点 即交换后两个结点被换至到后面的结点
}
return dummyNode.next; // 返回真实头结点
}
}
// 递归方法
// 递归至最后两个不为空可以互换的结点处 进行互换 · 完成后返回 交换后两个结点被换至到前面的结点
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) { // 两个不为空可以互换的结点
return head;
}
ListNode p = head.next; // 两个交换结点中偏 后面的结点
ListNode q = swapPairs(p.next); // 先交换链表后方的两两结点 返回交换后两个结点被换至到前面的结点
// 开始交换两两结点
p.next = head; // 后结点指针域 指向 前结点
head.next = q; // 前结点指针域 指向 后两两结点交换完成后的前结点
// 交换两两结点完成
return p; // 返回两两结点交换完成后的前结点
}
}
-
-
删除链表倒数第n个结点
-
19.删除链表倒数第n个结点
-
-
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
// 双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。 fast和slow始终保持n步的距离
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(0, head); // 新建虚拟头结点
ListNode p = dummyHead; // 前指针
ListNode q = dummyHead; // 后指针
ListNode pre = null; // 保存前指针前继结点 方便删除
while (n-- > 0) { // 先让后指针移动n步
q = q.next;
}
while (q != null) { // 然后让p和q同时移动,直到q指向链表末尾。删掉p所指向的节点就可以了
pre = p; // 保存前指针前继结点 方便删除
q = q.next; // 移动后指针
p = p.next; // 移动前指针
}
pre.next = p.next; // 删除p指针结点 即删除倒数第 n 个结点
return dummyHead.next;
}
}
-
-
链表相交
-
面试题02.07.链表相交
-
-
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
情况—:两个链表相交
链表headA和headB的长度分别是m和n。假设链表head.A的不相交部分有α个节点,链表headB的不相交部分有b个节点,两个链表相交的部分有c个节点,则有α+c= m,b+C= n。
·如果a= b,则两个指针会同时到达两个链表相交的节点,此时返回相交的节点;
·如果a≠b,则指针 pA 会遍历完链表headA,指针pB会遍历完链表headB,两个指针不会同时到达链表的尾节点,然后指针pA移到链表headB的头节点,指针 pB移到链表headA的头节点,然后两个指针继续移动,在指针pA移动了α+c+b次、指针 pB移动了b+c+α次之后,两个指针会同时到达两个链表相交的节点,该节点也是两个指针第一次同时指向的节点,此时返回相交的节点。
情况二:两个链表不相交
链表headA和headB的长度分别是m和n。考虑当m =n和m≠n时,两个指针分别会如何移动;·
如果m=n,则两个指针会同时到达两个链表的尾节点,然后同时变成空值null,此时返回null;
。如果m≠n,则由于两个链表没有公共节点,两个指针也不会同时到达两个链表的尾节点,因此两个指针都会遍历完两个链表,在指针 pA移动了m+n次、指针 pB移动了n+m次之后,两个指针会同时变成空值null,此时返回null。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) { // 两个单链表都不能为空 否则不会相交
return null;
}
ListNode A = headA;
ListNode B = headB;
while (A != B) { // 寻找相交结点
A = A != null ? A.next : headB; // 遍历完A链表再遍历B链表不相交部分 即m+c+n
B = B != null ? B.next : headA; // 遍历完B链表再遍历A链表不相交部分 即n+c+m
}
return A;
}
}
-
-
环形链表
-
141.环形链表1
-
-
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
// 一个快指针 一个慢指针 快指针每次走两步 慢指针每次走一步 如果链表存在环 快慢指针一定会相遇
// 当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode p = head; // 慢指针
ListNode q = head; // 快指针
while (q != null && q.next != null) { // 快指针后继结点不能为空 否则就是此链表无环
p = p.next; // 慢指针每次走一步
q = q.next.next; // 快指针每次走两步
if (p == q) { // 链表存在环 快慢指针一定会相遇 此时pq都为相遇点
return true;
}
}
return false; // 此链表没有环
}
}
// 拓展 如果存在环则环的长度为多少?
// 方法是,快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度。
// 当第一次相遇时 记录打开一个标识位 两指针继续往下走环 同时标识位打开开始记录环的长度 当两指针第二次相遇时 表明慢指针已经将环走了一遍 记录下来的长度即为环的长度
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode p = head; // 慢指针
ListNode q = head; // 快指针
bollean b = false; // 标识位
int size = 0; // 环的长度
while (q != null && q.next != null) { // 快指针后继结点不能为空 否则就是此链表无环
p = p.next; // 慢指针每次走一步
q = q.next.next; // 快指针每次走两步
if (p == q) { // 链表存在环 快慢指针一定会相遇 此时pq都为相遇
if (b) reture size; // 第二次相遇 已收集完环的长度 退出
b = true; // 第一次相遇 打开标识位 开始记录环的长度
}
if (b) { // 打开标识位 开始记录环的长度
size ++; // 记录环的长度 size ++;
}
}
return false; // 此链表没有环
}
}
-
-
-
142.环形链表2
-
-
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
// 先证明此链表有没有环 在找环的入口
// 一个快指针 一个慢指针 快指针每次走两步 慢指针每次走一步 如果链表存在环 快慢指针一定会相遇 此时证明了链表是存在环的
// 找环的入口
// 根据:
// 1. f = 2s (快指针每次2步,路程刚好2倍) f = 快指针移动步数 s = 慢指针移动步数
// 2. f = s + nb (相遇时,刚好多走了n圈) b = 环长度 n = 快指针多走了n圈
// 推出:s = nb
// 从head结点走到入环点需要走 : a + nb, (先走 a 步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点)。
// 而slow已经走了nb,那么slow再走a步就是入环点了。 (推出:s = nb)
// 如何知道slow刚好走了a步? 从head开始,和slow指针一起走,相遇时刚好就是a步 (slow指针位置不变 ,将fast指针重新指向链表头部节点 ;slow和fast同时每轮向前走 1 步 ; 当 fast 指针走到f = a 步时,slow 指针走到步s = a + nb,此时 两指针重合,并同时指向链表环入口 。)
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode q = head;
ListNode p = head;
while (p != null && p.next !=null) { // 证明有环
q = q.next;
p = p.next.next;
if (q == p) { // 第一次相遇点 开始找环入口
ListNode last = p; // p指针位置不变
ListNode first = head; // 将first指针指向链表头部节点
while (last != first) { // 两指针重合,并同时指向链表环入口
last = last.next; // last和first同时每轮向前走 1 步
first = first.next; // 直到重合即环入口
}
return last; // 指向链表环入口
}
}
return null; // 无环无入口
}
}