24. 两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
思路
使用虚拟头节点,交换的步骤一定要弄清楚,建议画图。
一次交换分为四步:
- pre.next = node2;
- node2.next = node1;
- node1.next = node3;
- pre = node1;
按照上图步骤操作完了之后得到下面这张图:
知道步骤之后,代码就很好写了!
代码实现
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyNode = new ListNode(-1, head);
ListNode pre = dummyNode;
ListNode temp = null;
ListNode first = null;
ListNode second = null;
while (pre.next != null && pre.next.next != null) {
temp = pre.next.next.next;
first = pre.next;
second = pre.next.next;
pre.next = second;
second.next = first;
first.next = temp;
pre = first;
}
return dummyNode.next;
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n **个结点,并且返回链表的头结点。
思路
使用快慢指针;
首先让快指针先移动 n + 1步;n + 1步是因为当我们需要删除一个节点的时候,我们需要获得该节点的前置节点。
然后同时移动快慢指针直至快指针指向了null。
然后我们就可以进行节点的删除。
代码实现
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if (n == 0) {
return head;
}
ListNode dummy = new ListNode(-1, head);
ListNode fast = dummy;
ListNode slow = dummy;
for (int i = 0; i <= n; i++ ) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
面试题 02.07. 链表相交
思路
我们首先遍历两个链表,求出它们的长度的差值diff。如下面这个示例,其差值为1。
然后我们让较长的那个链表先移动diff步,这时链表B中剩余的节点数量就和链表A中的节点数量一致了。
然后我们同时移动A, B 两个链表的指针,并比较他们是否为同一个节点;如果是我们就找到了相交点,如果一直到链表结束都没有找到,那么就代表这两个链表不相交。
代码实现
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) {
curA = curA.next;
lenA++;
}
while ( curB != null) {
curB = curB.next;
lenB++;
}
curA = headA;
curB = headB;
if (lenB > lenA) {
int temp = lenA;
lenA = lenB;
lenB = temp;
ListNode tempNode = curA;
curA = curB;
curB = tempNode;
}
int diff = lenA - lenB;
for (int i = 0; i < diff; ++i) {
curA = curA.next;
}
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
- 时间复杂度:O(n + m)
- 空间复杂度:O(1)
142. 环形链表 II
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 不允许修改 链表。
思路 - 快慢指针
首先我们需要定义两个指针:
快指针-每次移动两格子
慢指针-每次移动一个格子
这样定义的目的是:如果链表中存在环,那么快慢指针都进入环以后,快指针实际上是以每个循环一格的速度在接近慢指针,这样只要有环那么快慢指针就一定会在某一时刻重合。
判断是否有环
要判断有没有环也非常简单,如果有环,那么快慢指针就一定会相等,如果没有环那么快慢指针就永远不会相等,因此当快指针遍历完整个链表快慢指针还没有相等的话,那么就可以断定这个链表中不存在环!
找出环形入口节点
最后我们需要用数学推理来帮助我们得到进入环的节点,如下图:
我们首先假设连边内有环,x代表从头节点到进入环所需的节点数,y为从环形入口节点到 fast指针与slow指针相遇节点所需的节点数。z为从相遇节点到环形入口节点所需的节点数。
因为快指针会先进入环,因此当快慢指针相遇时,快指针一定在环内走过了至少一圈,这里我们将圈数定义为
n并且n >= 1;
并且由上图我们可以得出:
慢指针到相遇所走过的节点数为x + y
快指针到相遇时所走过的节点数就为x + y + n(y + z),而快指针的速度是慢指针的两倍,因此快指针所走过的节点数也可以写为2(x + y)
由此,我们得到等式:2(x + y) = x + y + n(y + z)
通过化简:
因为n >= 1,我们假设n = 1:
得到这个公式之后,当我们找到快慢指针相遇的节点时,把快指针重新指向头节点,慢指针保持在相遇节点的位置;这时同时移动他们,快慢指针的速度都为一次一格。当他们再次相遇时,就是环形入口节点。(因为x = z)
代码实现
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
ListNode temp = null;
while ( fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
fast = head;
while ( slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
}
- 时间复杂度:O(n), 快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
- 空间复杂度:O(1)