24.两两交换链表中的节点
题目 :给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
想法:通过这两天链表习题的训练,感觉在做链表习题时应当注意的几个点:
- 考虑下设置虚拟头结点是否更为方便(统一头结点和非头结点的操作);
- 考虑清楚终止循环条件应该怎么写(通过画图可以更清晰地反映);
- 空指针的问题,特别是在条件判断中
point != null
、point.next != null
和point.next.next
的先后顺序有没有考虑清楚; - 是不是需要设置几个临时节点来辅助链表指针的变动,也就是说要捋清楚链表指针变动的前后顺序。
- 注意区分真正的头结点,是
dummyhead
还是原来的head
- 能不能通过设置快慢指针(他们的速度可以相同也可以不同)来更加便捷的解决问题,
基于上述注意事项来看这道题
思路:
- 这道题显然设置一个虚拟头结点会更加方便对于头结点的操作;
- 这道题目的指针变动是非常值得考量的地方,我们要对两个单循环链表节点进行操作,就必须获得这两个节点的前一个节点,然后才能进行操作迭代。
- 循环条件:由于是要对两个节点进行操作,对于奇数节点来说,最后一个节点将不会被改动,也就是说当
currentPoint.next.next == null
时(不要忘记了第二条,我们需要知道这两个节点的前一个节点来进行操作所以这里会有两个next
),将终止循环;对于偶数节点来说,当当前节点的下一个节点为空时,也就是说当currentPoint.next == null
时,将终止循环 - 这里会有个坑,需要把
currentPoint.next == null
写在前面,也就是说条件是currentPoint.next == null && currentPoint.next.next == null
,这里如果反着写的话currentPoint.next == null
可能会报告空指针异常
需要思考一下如何进行链表指针的变动,才能顺利地进行改动,这里给张图以更详细的说明,如下图所示。
代码实现如下:
/**
* 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 temp0 = null;
ListNode temp1 = null;
ListNode dummyNode = new ListNode();
dummyNode.next = head;
ListNode cur = dummyNode;
while(cur.next != null && cur.next.next != null){
temp0 = cur.next;
temp1 = cur.next.next.next;
cur.next = cur.next.next;//上图步骤1
cur.next.next = temp0;//上图步骤2
temp0.next = temp1;//上图步骤3
cur = cur.next.next;//上图步骤4
}
head = dummyNode.next;//注意区分真正的头结点
return head;
}
}
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; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyhead = new ListNode();
dummyhead.next = head;
ListNode cur = dummyhead;
int num = 0;
int i = -1;
while(cur != null){
num++;
cur = cur.next;
}
num -= 1;
cur = dummyhead;
while(i < num - n - 1 && cur != null){
cur = cur.next;
i++;
}
if(cur != null && cur.next != null){
cur.next = cur.next.next;
}
return dummyhead.next;
}
}
现在来介绍快慢指针的解法
思路: 设置一个快指针,让它先行n步,第n步之后,快慢指针一起行动,这样一来,等到快指针指向null
时,慢指针指向的便是需要删除的链表节点了。
但是,应当注意到的是,对于需要删除的节点,需要得到它的前一个节点,这就要求快指针要先行n+1步
代码实现如下:
/**
* 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 removeNthFromEnd(ListNode head, int n) {
ListNode dummyhead = new ListNode();
dummyhead.next = head;
ListNode fast = dummyhead;
ListNode slow = dummyhead;
int num = 0;
while(fast != null){
fast = fast.next;
num++;
if(num > n + 1){
slow = slow.next;
}
}
if(slow != null && slow.next != null){
slow.next = slow.next.next;
}
return dummyhead.next;
}
}
面试题 02.07.
题目: 给你两个单链表的头节点headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
刚开始思考的时候,想着通过两层循环 + 判断语句来实现,第一层固定第一个链表的节点,第二层来寻找,找到地址一样的便返回,时间复杂度为O(n^2)
,然后就没经住压力测试,代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode dummyheadA = new ListNode();
dummyheadA.next = headA;
ListNode up = dummyheadA;
ListNode dummyheadB = new ListNode();
dummyheadB.next = headB;
ListNode down = dummyheadB;
while(up != null && up.next != null){
while(down != null && down.next != null){
System.out.println(down.next.val);
System.out.println(up.next.val);
System.out.println(down.next);
System.out.println(up.next);
if(down.next.val == up.next.val && down.next == up.next){
return down.next;
}
down = down.next;
}
down = dummyheadB;
up = up.next;
}
return null;
}
}
正经思路: 首先得到连个链表的长度,固定curA
为较长的链表,然后移动指针curA
,是两个链表尾部对齐,然后同时遍历两个链表,判断为true
之后即可返回
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0;
int lenB = 0;
while(curA != null){
lenA++;
curA = curA.next;
}
while(curB != null){
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
if(lenA < lenB){
int temp = lenB;
lenB = lenA;
lenA = temp;
ListNode tempNode = curB;
curB = curA;
curA = tempNode;
}
int gap = lenA - lenB;
for(int i = 0; i < gap; i++){
curA = curA.next;
}
while(curA != null && curB != null){
if(curA == curB){
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
142. 环形链表 II
题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
在刚开始想的时候,有想到通过设置快慢指针,并且他们的速度不同(速度差1)来遍历,当两个指针相遇时,说明链表有环,但是在寻找环的入口的时候,处理的不太好
**思路:**在相遇之后,令慢指针回到虚拟头结点,快指针速度设置为1,他们再次相遇便是环的入口。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode dummyhead = new ListNode();
dummyhead.next = head;
ListNode fast = dummyhead;
ListNode slow = dummyhead;
while(fast != null && fast.next != null && slow != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
ListNode index1 = fast;
ListNode index2 = dummyhead;
while(index1 != index2){
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}