一、力扣题24. 两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3]
示例 2:
输入:head = [] 输出:[]
示例 3:
输入:head = [1] 输出:[1]
提示:
- 链表中节点的数目在范围
[0, 100]
内 0 <= Node.val <= 100
本题主要要对节点交换的顺序了解清楚,这一点很容易搞混。
如图,在交换1,2两个节点后,1 节点要指向 3 节点,而为了统一步骤,就设置了虚拟头结点,将虚拟头结点指向 2 节点, 这样每次交换节点就都是三个步骤,不需要专门为头两个节点设置特别的步骤。
如图,每交换两个节点就需要用到三个步骤,把步骤搞清楚就可以了。需要注意的是,在交换节点的循环里,循环条件需要先验证交换的两个节点都不为空。
我之前步子跨太大,想直接把 1 节点指向 4 节点,这样就省了步骤 1 ,也不需要用到虚拟头结点了, 但是这样会造成空指针异常,如果这条链表只有两个节点,也就是没有 3,4节点时,节点1 ->next 为null, 那么1 ->next->next 为 null ->next,就会报错了。
我也想过把循环条件增加为判断 <交换的两个节点和它的下一个节点> 共三个节点不为空才能进去,但是这样就有了个问题,如果一个链表只有三个节点,那这个循环就进不去,也就无法交换前两个节点了,故舍弃这个想法。
方法:
function swapPairs($head) {
$cur = $head;
$behind = $cur->next;
if($cur == null || $behind == null) {
return $cur;
}
$head = $head->next;
$prev = new ListNode();
while($behind != null && $cur != null) {
$prev->next = $behind;
$temp = $behind->next;
$behind->next = $cur;
$cur->next = $temp;
$prev = $cur;
$cur = $temp;
$behind = $cur->next;
}
return $head;
}
二、力扣题 19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 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]
提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
方法一:暴力解
我自己写的时候用的比较简单的方法,先遍历一遍链表,获取到链表的节点个数,然后再重新遍历一遍删除链表中的目标节点。为了能删除头结点,也用了虚拟头结点。
function removeNthFromEnd($head, $n) {
$dummyHead = new ListNode();
$dummyHead->next = $head;
$cur = $head;
$i = 0;
while($cur->next) {
$cur = $cur->next;
$i++;
}
$j = 0;
$cur = $dummyHead;
while($j <= $i - $n) {
$cur = $cur->next;
$j++;
}
$cur->next = $cur->next->next;
return $dummyHead->next;
}
方法二:双指针
去看了讲解,发现双指针还能这么玩,自己对双指针的应用和了解还是不够。
先定义两个快慢指针fast 和 slow,都放在虚拟头结点,fast 先走 n 步,然后fast 和 slow 再同时移动,当 fast 走到最后一个节点时,slow 刚好停在要删除的目标节点的上一个节点,然后就可以删除了。
(原讲解是 fast 先走的是 n+1 步,之后当fast 指向null时,slow 刚好在目标节点的上一个节点,结果都一样,只是对fast 的判断条件不同。)
function removeNthFromEnd($head, $n) {
$dummyHead = new ListNode();
$dummyHead->next = $head;
$fast = $dummyHead;
$slow = $dummyHead;
while($n > 0) {
$fast = $fast->next;
$n--;
}
while($fast->next) {
$fast = $fast->next;
$slow = $slow->next;
}
$slow->next = $slow->next->next;
return $dummyHead->next;
}
三、力扣题160. 相交链表 (面试题)
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
方法一:暴力解
一如既往的循环遍历,把其中一个链表的遍历作为外循环,另一个作为内循环,然后遍历找到相交点。时间复杂度应该是O(n * m), 这个真的好耗时,用了三千多ms的时间。
需要注意的是,如果不同节点 headA 和 headB 的值相同,那么 headA == headB 也会成立,判断条件需要是 headA === headB 才能判断出来是不是同一个节点。
function getIntersectionNode($headA, $headB) {
$b = $headB;
while($headA) {
$headB = $b;
while($headB) {
if($headA === $headB) {
return $headA;
}
$headB = $headB->next;
}
$headA = $headA->next;
}
return null;
}
方法二:对齐后再一同移动
去看了讲解发现了更精妙的解法, 感觉以后要多从数学的角度来解题目。
先把两个链表各自遍历一遍,获取两个链表的节点个数,然后相减获得差值 n ,再把 fast 指向其中较长的链表的头结点,fast 先移动 n 步,然后再和另一条链表的头结点同时移动,遍历判断出相交点。 时间复杂度为O(n + m + z ), 比上一个方法省时很多。
function getIntersectionNode($headA, $headB) {
$a = 0;
$temp1 = $headA;
$temp2 = $headB;
while($temp1) {
$temp1 = $temp1->next;
$a++;
}
$b = 0;
while($temp2) {
$temp2 = $temp2->next;
$b++;
}
$num = abs($a - $b);
if($a > $b) {
while($num > 0) {
$headA = $headA->next;
$num--;
}
} else {
while($num > 0) {
$headB = $headB->next;
$num--;
}
}
while($headA) {
if($headA === $headB) {
return $headA;
}
$headA = $headA->next;
$headB = $headB->next;
}
return null;
}
四、力扣题142. 环形链表 II
给定一个链表的头节点 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 解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 104]
内 -105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引
方法一:暴力解
依旧是遍历为主,将链表中的每个节点的next与这个节点之前的所有节点对比判断。
设置两个循环,外循环遍历一个个节点,遍历到这个节点时,会记录这个节点在链表里是第几个,然后在内循环里设置的循环次数就是这个节点在链表里的位置,如果这个节点排第七个,就会循环六次,逐一判断这个节点的next是否指向前面六个节点。
· 时间复杂度为 O( n*(1+n)*n/2 ),耗时两千多毫秒。
function detectCycle($head) {
$cur = $behind = $temp = $head;
$i = 0;
while($behind) {
$j = $i;
$temp = $cur;
while($j > 0) {
if($behind->next == $temp) {
return $temp;
}
$temp = $temp->next;
$j--;
}
$behind = $behind->next;
$i++;
}
return null;
}
方法二:分析出规律
看的讲解的分析过程有点复杂,这里直接贴链接
设置两个快慢指针,fast和slow同时走,fast 每次走两步,slow每次走一步,fast每次指向的节点位置都是偶数,slow则是偶奇数都有,又因为是环形链路,所以这两个指针一定会相遇。
而通过以上链接里的分析可得出,如果一个指针one 在链路的头结点,一个指针two在快慢指针的相交节点上,两个指针同时移动,每次移动一步,最后相遇的节点,一定是链路开始入环的第一个节点。
function detectCycle($head) {
$fast = $head;
$slow = $head;
while($fast) {
$slow = $slow->next;
$fast = $fast->next->next;
if($slow == $fast) {
while($head != $slow) {
$head = $head->next;
$slow = $slow->next;
}
return $head;
}
}
return null;
}