目录-链表相关
24. 两两交换链表中的节点
题目描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
思路
交换两个结点,改变他们的next的指针方向。但是对于处在头部和中间部分的结点,经过的步骤是不一样的。如下图,交换第一个和第二个结点,需要两步。
当交换第三个和第四个结点的时候,需要三步(如下图)。
所以为了统一处理,利用虚拟头结点,给链表前面增加一个虚拟头结点,这样交换两个结点统一处理需要三个步骤。
算法核心
- 如前面图中所示,交换两个结点,需要改变这两个结点前面一个结点的next指针,所以要得到指向这个结点的指针。
- 当交换完当前两个结点后,使指针移动到下两个需要交换的结点的前一个结点。继续1的操作。
- 循环终止条件,当交换的结点没有或者剩下一个的时候终止循环。
代码&复杂度
/**
* 模拟交互过程,借助虚拟头指针
* @param head
* @return
*/
public ListNode swapPairs(ListNode head) {
if(head == null){
return null;
}
if(head.next == null){
return head;
}
ListNode dummyNode = new ListNode(0); // 加入虚拟头节点,使头部操作和中间操作保持一致,否则需要单独处理头节点
dummyNode.next = head;
ListNode cur = dummyNode; // 交换两个节点,需要得到这两个节点的前一个节点
ListNode temp;
while(cur.next != null && cur.next.next != null){// 要交换的两个节点同时不为null才执行
//注意要画图,指针指向容易乱
temp = cur.next;
cur.next = cur.next.next;
temp.next = cur.next.next;
cur.next.next = temp;
cur = cur.next.next; // cur指到需要交换的下两个节点的前一个节点
}
return dummyNode.next;
}
时间复杂度:O(n)
空间复杂度:O(1)
19.删除链表的倒数第N个节点
题目描述
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
思路
快慢指针法:使fast指针先移动n步,然后让slow和fast同时移动,当fast移出末尾停止移动,删掉slow指针所指向的结点。
注意:如果删除的是头结点,要单独处理,也可以利用增加虚拟头结点的方法。
算法核心
- 先移动fast指针n位。
- slow指针和fast同步移动。当fast移出末尾,停止移动。
- 删掉slow结点。注意删除某一个结点,要知道前一个结点。还要注意头结点的删除,需要特殊处理。
代码&复杂度
/**
* 没用虚拟头结点,注意如果删除的是头结点,要单独处理
* @param head
* @param n
* @return
*/
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast = head;
ListNode slow = head;
ListNode prev = null;
int count = 0;
while(fast != null){
// 先移动fast n位
fast = fast.next;
count++;
if(count <= n){
continue;
}
prev = slow;
slow = slow.next;
}
if(prev == null){
//删除的是头节点
head = head.next;
return head;
}
prev.next = slow.next;
return head;
}
面试题 02.07. 链表相交
题目描述
给你两个单链表的头节点 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 个节点。
思路
当两个链表长度相同时,假设有两个指针分别指向这两个链表,并且以同样的速度移动,这两个指针肯定会同时到达相交的点,每移动一步判断这两个结点的地址是否相同,相同则就是相交的点。如果两个链表长度不同时,让长的那个链表先移动,使他们的起点相同(右边对齐),在同时移动。
算法核心
- 分别求两个链表的长度。
- 用fast指针指向长的链表,slow指向短的链表。
- 让fast指针先移动他们的长度的差值。
- fast和slow同时移动,并判断指向的结点是否相同。此过程反复操作,直到链表遍历完。
代码&复杂度
/**
* 求两个链表的相交点
* 在两个链表右边对齐的情况下,保证两个链表移动起点相同,然后比对两个链表的结点是否相等
* 时间复杂度:O(m+n), 空间复杂度:O(1)
* @param headA
* @param headB
* @return
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null){
return null;
}
//分别求两个链表的长度
int lengthA = 0, lengthB = 0;
ListNode curA = headA;
ListNode curB = headB;
while(curA != null) {
lengthA++;
curA = curA.next;
}
while(curB != null) {
lengthB++;
curB = curB.next;
}
ListNode fast, slow = null;
int distance = 0;
// 长度大的链表用一个快指针指向,小的用一个慢指针指定
if(lengthA > lengthB){
fast = headA;
slow = headB;
distance = lengthA - lengthB;
}else{
fast = headB;
slow = headA;
distance = lengthB - lengthA;
}
// 快指针先移动,保证和慢指针指向的链表相同起点(右边对齐)
while(distance > 0){
fast = fast.next;
distance--;
}
// 相同结点则返回
while(slow != null && fast != null){
if(slow == fast){
System.out.println("交叉结点的值为:" + slow.val);
return slow;
}
slow = slow.next;
fast = fast.next;
}
return null;
}
时间复杂度:O(m+n)
空间复杂度:O(1)
注意
- 交叉结点指得是该结点的地址相同,而不是里边的val相同。
142.环形链表II
题目描述
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
思路
- 判断有无环:可以用快慢指针,相遇则有环。注意要保证fast指针移动速度比slow快一步,要不然不会相遇。可以让fast一次移动2步,slow移动1步。
- 怎么求环的入口?当相遇指针和起始指针同时移动再相遇时,就是环的入口。
代码&复杂度
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
// 相遇了,有环
ListNode cur = head;
ListNode meetNode = fast;
while(cur != meetNode){
//当相遇结点和起始结点指针同时移动并相遇时,即是环形入口
cur = cur.next;
meetNode = meetNode.next;
}
return cur;
}
}
return null;
}
注意
- 涉及到数学知识,要清楚计算过程。