链表 part02
1. 24 两两交换链表节点 M
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
1. 1使用虚拟头节点
1.1.1 思路分析
-
代码终止条件是什么?
如上图,第一次交换完后,cur指向1的时候,接下来3与4交换,此后cur指向交换后的3,这时候只剩下5不能交换。
奇数:
cur .next .next=null
偶数:
cur.next=null
或者空节点的话也是这么个条件 -
每一次交换完后,cur指向下一次交换的前一个节点
-
两个交换节点需要提前保存第一个,以及这两节点之后的另一个节点
1.1.2. 解法
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dumyhead = new ListNode(-1); // 设置一个虚拟头结点
dumyhead.next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
ListNode cur = dumyhead;
ListNode temp; // 临时节点,保存两个节点后面的节点
ListNode firstnode; // 临时节点,保存两个节点之中的第一个节点
ListNode secondnode; // 临时节点,保存两个节点之中的第二个节点
while (cur.next != null && cur.next.next != null) {
temp = cur.next.next.next;
firstnode = cur.next;
secondnode = cur.next.next;
cur.next = secondnode; // 步骤一
secondnode.next = firstnode; // 步骤二
firstnode.next = temp; // 步骤三
cur = firstnode; // cur移动,准备下一轮交换
}
return dumyhead.next;
}
}
1.2 递归
1.2.1 思路分析
使用递归来解决该题,主要就是递归的三部曲:
- 找终止条件:本题终止条件很明显,当递归到链表为空或者链表只剩一个元素的时候,没得交换了,自然就终止了。
- 找返回值:返回给上一层递归的值应该是已经交换完成后的子链表。
- 单次的过程:设需要交换的两个点为
head
和next
,head
连接后面交换完成的子链表,next
连接head
,完成交换
1.2.2 解法
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null ||head.next == null) {
return head;
}
ListNode next = head.next;
//递归
ListNode newNode = swapPairs(next.next);//子链表返回新的头节点newNode
//交换
next.next = head;
head.next = newNode;
//返回新的链表的头节点
return next;
}
}
- 时间复杂度:O(n),其中 n 是链表的节点数量。需要对每个节点进行更新指针的操作。
- 空间复杂度:O(n),其中 n 是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。
2. 19 删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。
2.1 思路分析
- 设置一个虚拟头节点,使得删除第一个和其他节点操作一样
- 采用快慢指针
- 让快指针先往前移动,只要快慢指针相差n个节点
- 因为加了一个虚拟头节点,所以先让快指针移n+1个节点,之后快慢指针同时移动,直到快指针移动到空,此时slow指向要删节点的前驱
2.2 双指针解法
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//创建一个虚拟头节点,并指向头节点
ListNode dummyNode=new ListNode(0,head);
//创建快慢指针
ListNode fast=dummyNode;
ListNode slow=dummyNode;
//因为有虚拟头结点了,所以移动n+1个节点
for (int i = 0; i < n+1; i++) {
fast=fast.next;
}
/* //在移动一个节点
fast=fast.next;*/
//快慢指针同时移动
while (fast!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummyNode.next;
}
}
3. 160 相交链表
给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null
。
3.1 思路分析
- 先求出A B两链表长度差,然后让长链表移动到与锻炼表平齐,长短两表一起移动,直到curA=curB;
3.2 解题代码
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
//A B长度
int lenA = 0, lenB = 0;
//求A长度
while (curA != null) {
lenA++;
curA = curA.next;
}
//B长度
while (curB != null) {
lenB++;
curB = curB.next;
}
//当前两节点指向A B
curA = headA;
curB = headB;
//统一A长度大于B
if (lenB > lenA) {
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
//求长度差
int gap = lenA - lenB;
//让curA和curB在同一起点(末尾位置对齐)
while (gap != 0) {
curA = curA.next;
gap--;
}
//遍历curA和curB,遇到相同则直接返回
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
- 时间复杂度:O(n + m)
- 空间复杂度:O(1)
3.3另一种方法——双指针(思路绝了!!)
3.3.1 思路分析
这个真的没想到,啥也不说了,看力扣解答吧:链接,这不是我等凡人能想出来的,呜呜呜…
3.3.2 解题代码
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
/*
A走完回B出发点,B走完回A出发点,根据走过的节点数一致即可判断的
相交的在相交节点处相遇,否则在null相遇
*/
ListNode A = headA, B = headB;
while (A != B) {
//判断A或B是否为空,然后取后面问号的值,并赋给A,B
// 细节:需要将空节点包含进路径中
A = A != null ? A.next : headB;
B = B != null ? B.next : headA;
}
return A;
}
}
- 时间复杂度 O(a+b): 最差情况下(即 ∣a−b∣=1|a - b| = 1∣a−b∣=1 , c=0c = 0c=0 ),此时需遍历 a+ba + ba+b 个节点。
- 空间复杂度 O(1): 节点指针 A , B 使用常数大小的额外空间。
4. 142 环形链表 II M
给定一个链表的头节点
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回null
。
4.1 思路分析
-
判断链表是否有环?
快慢指针法,分别定义fast和slow指针,从头结点出发,fast指针每次移动两个结点,slow指针每次移动一个结点,如果途中相遇,说明该链表有环。
-
如果有环,如何找到环入口?
从头结点发出一个指针,从相遇结点发出一个指针,这两个指针每次只走一个结点,那么当这两个指针相遇的时候就是环形入口的结点。
4.2 双指针解法
public class Solution {
public ListNode detectCycle(ListNode head) {
//设置快慢指针
ListNode slow=head;
ListNode fast=head;
//快慢指针开始移动,快指针每次移动两节点,慢指针每次移动1节点
while (fast != null && fast.next != null) {//为什么也要判断 fast.next != null?因为快指针每次移动2步,所以要判断它下一个是否为空
fast=fast.next.next;
slow=slow.next;
//快慢指针相遇了
if (fast==slow) {//相等说明有环啦~
ListNode index1=fast;//相遇节点 因为这时候快慢指针是一块了
ListNode index2=head;//头结点
//两个指针,从头结点和相遇结点,各走一步,直到相遇,即为环入口(此处证明网上有说明)
while (index1 != index2) {
index1=index1.next;
index2=index2.next;
}
//返回入环第一个节点
return index1;
}
}
return null;
}
}
-
时间复杂度:O(N)
- 快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
-
空间复杂度:O(1)
- 只使用了 slow,fast,index1,index2 四个指针。