链表
移除链表元素
题目
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
思路
- 注意这里是不带头节点的链表,所以对应头结点的处理要尤其重视**(所以还是申请带头结点的链表好)**
- 头结点为空的情况需要处理
- 头结点的值刚好为val的时候需要处理
- 一般是保留删除的节点的前一个节点信息,这样方便接链和断链
具体实现由三种思路
- 直接处理,对特殊情况(头结点值刚好为需要删除的那个值)进行讨论。
- 申请一个新的头结点,然后就可以不用讨论特殊情况。
- 递归实现,如果能够理解递归,算法很简洁。
代码一:直接对特殊情况进行处理
//思路一:直接处理,对特殊情况(头结点值刚好为需要删除的那个值)进行讨论。
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null) return head;
//特殊情况处理,注意这里不是if,不断判断当前头结点是否满足情况
while(head != null && head.val == val) head = head.next;
ListNode p = head;
//注意while循环条件,同时只需要申请一个节点遍历链表就可以
while(p != null && p.next != null){
if(p.next.val == val){
p.next = p.next.next;
}
else{
p = p.next;
}
}
return head;
}
};
代码二:申请一个新的头结点
//思路二:申请一个新的头结点,然后对新的头结点进行处理,可以免除特殊情况讨论
class Solution {
public ListNode removeElements(ListNode head, int val) {
//申请一个新的头结点,省去了头结点的特殊情况处理
ListNode dhead = new ListNode(0, head);
ListNode p = dhead;
while(p != null && p.next != null){
if(p.next.val == val){
p.next = p.next.next;
}
else{
p = p.next;
}
}
//注意返回的是不带头结点的链表,所以要返回next
return dhead.next;
}
};
代码三:递归
class Solution {
public ListNode removeElements(ListNode head, int val) {
//递归实现
//首先是递归出口,如果是null,直接返回
if(head == null) return null;
//然后进行递归
head.next = removeElements(head.next, val);
//递归结束后,到最末尾出栈判断进行返回
if(head.val == val) return head.next;
else return head;
}
}
反转链表
题目
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
思路
-
申请头结点之后,采用头插法实现链表的逆序
-
头插法,将一个新的节点插入到头结点之后,大致分为四个步骤
- 保存第二个节点
- 当前节点指针的指向置为头结点指针的指向
- 头结点指针指向当前头结点
- 更新当前节点为保存的第二个节点
-
代码:
class Solution {
public ListNode reverseList(ListNode head) {
//反转链表
ListNode newhead = new ListNode(0,null);
ListNode p = head;
while(p!=null){
ListNode q = p.next;
p.next = newhead.next;
newhead.next = p;
p = q;
}
return newhead.next;
}
}
两两交换链表中的节点
题目
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
思路
- 直接进行模拟即可
- 注意需要申请一个新的头结点,避免对于头结点的单独讨论
- 接三个链
- 重新更新指针
- 注意循环结束条件
代码:
class Solution {
public ListNode swapPairs(ListNode head) {
//申请头结点
ListNode newhead = new ListNode(0,head);
//只有一个节点或者只是空节点,直接返回head
if(head == null || (head != null && head.next == null)) return head;
ListNode pre = newhead;
ListNode node = pre.next;
ListNode nextNode = node.next;
while(node != null && node.next != null){
//接链和断链
node.next = nextNode.next;
nextNode.next = node;
pre.next = nextNode;
//更新三个指针,重新设置pre为node,进行下一次的两两交换
pre = node;
if(pre.next == null) break;
node = pre.next;
if(node.next == null) break;
nextNode = node.next;
}
return newhead.next;
}
}
K个一组翻转链表
题目
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
提示:
- 链表中的节点数目为
n
1 <= k <= n <= 5000
0 <= Node.val <= 1000
**进阶:**你可以设计一个只用 O(1)
额外内存空间的算法解决此问题吗?
思路
- 对链表进行统计计数,首先申请一个节点用于遍历,每次遍历都进行计数
- 当计数器与k节点相同时,进行链表的逆转。这里逆转采用头插法进行
- 逆转完需要更新first指针和pre指针。
代码:
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
//k个一组翻转链表
int count = 0;
ListNode newhead = new ListNode(0, head);
ListNode p = head; //遍历指针
ListNode pre = newhead;
while(p != null){
p = p.next;
count++;
//计数器达到k个顶点,逆转链表
if(count == k){
//头插法逆转链表
ListNode q = pre.next; //q为遍历指针
ListNode first = pre.next; //first保存第一个节点,后序指针修改需要用到
while(q != p){ //头插法逆转链表
ListNode nextq = q.next;
q.next = pre.next;
pre.next = q;
q = nextq;
} //更新指针pre,修改first指针指向
first.next = p;
pre = first;
count = 0;
}
}
return newhead.next;
}
}
删除链表的倒数第N个节点
题目
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
思路
- 双指针
- 一个指针先走k步,然后另外一个指针指向链表的开头,然后两个指针一起走。
- 当一个指针走到了链表的末尾的时候,另外一个指针就走到了链表的导数第k个节点
- 注意由于是删除,所以需要保存倒数第k个节点的前一个节点,然后再进行删除。这里的操作是删除指针指向的新申请的头结点,这样就可以少走一步,最后指向了要删除节点的前一个节点。
代码:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//思路:先走k步,然后两个指针一起走,先走k步的到达终点说明另外一个节点
//特殊情况考虑,空节点或者只有一个节点直接返回null
if(head==null || (head !=null && head.next == null)) return null;
//p为先走k步的节点,del为找到删除节点的前一个节点,newhead是为了删除头结点的时候不出错
ListNode newhead = new ListNode(0, head);
ListNode p = head;
ListNode del = newhead;
//先走n步,保证后序del指向要删除的前一个节点
for(int i = 0; i < n; i++) p = p.next;
//一起往前走
while(p != null){
p = p.next;
del = del.next;
}
//删除
del.next = del.next.next;
//返回
return newhead.next;
}
}
链表相交
题目
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交**:**
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
思路
- 双指针
- 两个指针分别指向两个不同的链表头
- 一个指针如果结束后,指向另外一个指针的头结点
- 直到两个指针指向一起了,那么说明这个节点就是相遇的节点。(如果没有,同时指向空)
代码:
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//两个指针一起走,走到终点之后再去走另外一个链表
if(headA == null || headB == null) return null;
ListNode p = headA;
ListNode q = headB;
while(p != q){
if(p == null) p = headB;
else p = p.next;
if(q == null) q = headA;
else q = q.next;
}
return p;
}
}
环形链表II
题目
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
思路
- 双指针(快慢指针)
- 如果存在环,快指针一定可以追上慢指针。
- 如果快慢指针相遇说明存在环,这时候快指针重新指向头结点,然后两个指针一起往前走,相遇的节点就是环的入口。
- 如果快慢指针不相遇说明不存在环,直接返回null
代码:
public class Solution {
public ListNode detectCycle(ListNode head) {
//空节点或者没有节点
if(head == null || (head != null && head.next == null)) return null;
//快慢指针
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){ //相遇了,说明存在环
ListNode p = slow; //两个指针,一个指针指向慢指针或者快指针
ListNode pre = head; //一个指针指向头结点,然后一起走,直到相遇
while(pre != p){
pre = pre.next;
p = p.next;
}
return p; //返回相遇节点
}
}
return null;
}
}