Java链表
public 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;
}
}
一、移除链表元素 LeetCode链接
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
思路
方便代码书写,设置虚拟节点。运用双指针,pre指向虚拟节点,cur指向头结点。判断cur指向的节点数据是否等于val,若等于则直接让pre断开与cur的链接,指向cur的下一个节点,接着cur移动到该节点,pre不需要动;若不等于则让pre移动到cur处,cur后移一位。
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead.next;
ListNode pre = dummyHead;
while (cur != null){
if (cur.val == val){
pre.next = cur.next;
}else {
pre = cur;
}
cur = cur.next;
}
return dummyHead.next;
}
二、反转链表 LeetCode链接
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
注意是反转节点,而不是节点里的数据。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
思路
设置cur = head,pre = null。需要将head.next改为null,即cur.next = pre。此时两指针再往后移动时失去目标地址,于是设置tmp用于保存cur的下一节点。当cur反转后,cur可以通过tmp找到下一节点,先把pre移动到此时的cur处,接着cur向后移动。
public ListNode reverseList(ListNode head){
ListNode tmp = null,pre = null,cur = head;
while(cur != null){
tmp = cur.next;//保存cur的下一节点
cur.next = pre;//反转链表
pre = cur;//指针后移
cur = tmp;
}
return pre;//此时cur为null,pre指向旧链表的最后一个节点,成为反转链表后的头结点
}
三、 两两交换链表中的节点 LeetCode链接
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
思路
交换节点有三个步骤
(图片来自代码随想录,用于学习记录)
交换两个节点需在这前一个节点前放置一个指针cur用于连接节点。两个节点分别用pre1,pre2指向。节点交换,第二个节点移到前边,即cur.next = pre2;第一个节点移到后边,即pre2.next = pre1;接着把链表连接起来,但节点三因为和节点二断开连接无法通过next找到,所以用tmp保存节点三的地址,然后连接链表即 pre1.next = tmp。最后把cur移动到下两个需交换的节点前,即cur = pre1。
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(0,head);
ListNode cur = dummyHead;
ListNode pre1,pre2,tmp;
while(cur.next != null && cur.next.next != null){//保证后续有两个节点操作
pre1 = cur.next;//保存节点1
pre2 = cur.next.next;//保存节点2
tmp = pre2.next;//保存节点3
cur.next = pre2;//节点2移到前边
pre2.next = pre1;//节点1移到节点2后边
pre1.next = tmp;//节点3连接到节点2后边
cur = pre1;
}
return dummyHead.next;
}
其实代码可以简化,因为在开始时可以通过next找到节点二,所以可以不保存第二个节点,直接让cur.next = cur.next.next。之后思路和上边一样,让节点一移到节点二的后边,即cur.next.next = pre1,再后把节点三连接到节点二后边,即pre1.next = pre2(此时pre2定义为节点三)。
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(0,head);
ListNode cur = dummyHead;
ListNode pre1,pre2,tmp;
while(cur.next != null && cur.next.next != null){
pre1 = cur.next;//保存节点1
pre2 = cur.next.next.next;//保存节点3
cur.next = cur.next.next;//直接连接节点2
cur.next.next = pre1;//节点1移到节点2后边
pre1.next = pre2;//节点3连接到节点1后边
cur = pre1;
}
return dummyHead.next;
}
四、删除链表的倒数第N个节点 LeetCode链接
给你一个链表,删除链表的倒数第 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]
思路
设置一个头结点方便操作,运用双指针法(刚开始想法为算出链表长度,长度减去n,让指针从虚拟指针开始移动n-1次到该位置,但由于还要算链表长度,效率并不高)。想到了双指针,fast和slow都从虚拟指针开始,让fast先走n + 1步,然后让fast和slow一起走,直到fast为null,此时slow刚好指向需要删除的位置的前一个节点。只需一次遍历即可完成操作。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode fast,slow;
fast = slow = dummyHead;
while (n > 0){
fast = fast.next;
n--;
}
fast = fast.next;//fast走第n+1步
while (fast != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummyHead.next;
五、链表相交 LeetCode链接
给你两个单链表的头节点 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 ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode l1 = headA;
ListNode l2 = headB;
while (l1 != l2){//当遇到相同的节点时退出
l1 = l1 == null ? headB : l1.next;//链表A走完去链表B走,否则在A继续移动
l2 = l2 == null ? headA : l2.next;
}
return l1;
}
六、环形链表II LeetCode链接
给定一个链表的头节点 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
解释:链表中没有环。
思路
首先是怎么判断环形
在此复制代码随想录的原话
“可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。”
接着是怎么找到入口
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。最后 x = (n - 1) (y + z) + z。当x = z时,找到入口
“从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。”
更加详细的请到 代码随想录 (programmercarl.com)
public ListNode detectCycle(ListNode head) {
if (head == null)
return null;
ListNode slow,fast;
slow = fast = head;
while (fast.next != null && fast.next.next != null){//保证fast可以移动两步
slow = slow.next;
fast = fast.next.next;
if (slow == fast){//保证有环
ListNode cur = slow;
ListNode pre = head;
while (cur != pre){//相等时节点即为入口
cur = cur.next;
pre = pre.next;
}
return cur;
}
}
return null;
}
(文章大部分图片来自力扣)
练习!