24. 两两交换链表中的节点
文章链接
第一想法
题目要求是两两翻转节点,我的第一想法是双指针,通过指针的移动来不断修改链表,共有两个指针(pre和curr),pre指针指向两个节点翻转的前一个节点,curr指向两个节点翻转前的第一个节点.
代码如下:
type T=ListNode|null
function swapPairs(head: ListNode | null): ListNode | null {
let vhead:T=new ListNode()
vhead.next=head
let pre:T=vhead
let curr:T=vhead.next
while(curr&&curr.next){ //这里需要保证curr和curr.next存在 curr用于判断偶数链表的终止条件,curr.next用于终止奇数链表的终止条件
let node=curr.next//先把curr.next存起来
pre.next=node//第一步
curr.next=node.next//第二步
node.next=curr//第三步
//移动指针
pre=curr
curr=curr.next
}
return vhead.next
};
看完文章后的想法
文章的想法其实和第一想法是一致的,只不过代码随想录中只用到了一个指针,而我用到了两个指针,区别不大,但是也有所差异,因为翻转步骤不一样,代码随想录中的第二三步和我的二三步颠倒了,所以写代码时代码随想录中先存了三个步骤的节点,之后在翻转。其实意思是一样的,就不重复给出代码了.
思考
对于链表的题一定要把逻辑搞清楚,先干什么,后干什么都很重要。所以在做链表题时,如果不是对逻辑很清晰,可以通过画图来梳理逻辑,这样有助于帮助自己和他人理解。PS:虚拟节点真的很好用
19. 删除链表的倒数第 N 个结点
文章链接
第一想法
看到这道题我的第一想法是先算出链表有多长,然后找到删除节点的前一个节点,让next等于next.next。话不多说,上代码:
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
let vhead:T=new ListNode()
vhead.next=head
let pre:T=head //用于计算链表的长度
let count:number=0
while(pre){
count++
pre=pre.next
}
pre=vhead//重新用pre指针来找要删除节点的前一个节点,这里等于vhead是因为直接找到前一个节点
count-=n//减n是为了找到删除节点的前一个节点
while(count--){
pre=pre!.next//这里以及下面加了! 是为了防止编辑器提示,但是理论上来说pre不会是null的 不能用? 否则右侧的类型为ListNode|null|undefined
}
pre!.next=pre!.next!.next
return vhead.next
};
看完文章后的想法
一看代码随想录文章,就发现还是老生常谈的双指针,又是没想到,主体思路是有两个快慢指针,快指针先走n步(或N+1步,主要区别是终止条件一个是fast一个是fast.next),借用代码随想录中的图片:
代码如下(代码以提前走n步为例):
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
let vhead:T=new ListNode()
vhead.next=head
let fast:T=vhead
let slow:T=vhead
while(n--){//提前走n步
fast=fast!.next
}
while(fast!.next){//如果提前走n+1步 这里就应该是fast
fast=fast!.next
slow=slow!.next
}
slow!.next=slow!.next!.next
return vhead.next
};
进阶递归写法:
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
let vhead:T=new ListNode()
vhead.next=head
let count=0//用于计算回退到哪里了
const foo:(node:T)=>void=(node:T)=>{
if(node==null) return
foo(node.next)
count++//这里count++必须要在递归之后,递归之前没有意义
if(count==n+1) node!.next=node!.next!.next//这里要是n+1,因为要回退到删除节点的前一个节点
}
foo(vhead)
return vhead.next
};
思考
链表的题大多数都是用双指针可以解决的,看完文章后自己也用双指针的写法把题写出来了,唯一的不同就是要考虑清楚fast提前走几步和终止条件的关系。而在进阶写法中,count++一定要在递归之后,放在递归之前则count会是一个值且等于链表长度。
面试题 02.07. 链表相交
文章链接
第一想法
因为由题意可知,要判断节点是否相同,所以第一想法是模拟,第一层while来循环A,第二次while循环B,当A等于某个节点时,循环B依次比较节点是否相等,代码如下:
var getIntersectionNode = function(headA, headB) {
let pre=headA
let curr=headB
while(pre){//当A等于某个节点时,循环B依次比较节点是否相等
while(curr){
if(pre==curr) return pre
curr=curr.next
}
pre=pre.next
curr=headB
}
return null //如果没有找到相同节点就返回null
};
上方代码可以通过测试用例
看完文章后的想法
节点相等我忽略了一个重要条件,节点相同的话那么长度应该也是相同的。所以可以直接找到节点数目相同的时候再进行判断,代码如下:
var getIntersectionNode = function(headA, headB) {
let pre=headA
let curr=headB
let lenA=0
let lenB=0
//获取链表长度
while(pre){
lenA++
pre=pre.next
}
while(curr){
lenB++
curr=curr.next
}
//判断哪个链表长,且长的列表为pre 短的链表为curr
let n=lenA-lenB
pre=headA
curr=headB
if(lenA<lenB){
n=lenB-lenA;
[pre,curr]=[curr,pre]
}
//让长的链表的指针向后移动,知道指针指向的节点的长度和curr相同
while(n--){
pre=pre.next
}
while(pre){
if(pre===curr) return pre
pre=pre.next
curr=curr.next
}
return null
};
思考
这道题还是挺简单的,但是做题的时候忽略了一个重要的细节,就是节点相同时长度也相同,所以我模拟的话这道题偶然通过了,链表的题多尝试用双指针解还是比较好的。
142.环形链表II
文章链接
第一想法
因为要判断是否有环,所以先把每走过的节点都标记一下,当走到有标记的节点之前,则是环的入口,如果节点走完都没有标记的节点的话,则说明无环,代码如下:
function detectCycle(head: ListNode | null): ListNode | null {
let map=new Map() //用map来标记节点
let pre:ListNode | null=head
while(pre){
if(map.has(pre)) return pre //判断是否已经走过这个节点
map.set(pre,pre)
pre=pre.next
}
return null //无环时输出
};
看完文章后的想法
只能说非常妙,考了非常多的数学知识。首先我们要确定是否有环,文章用的方法非常妙,用快指针一次走两个,慢指针一次走一个,如果存在环,则快慢指针一定会相遇(例如在操场跑步,快的人一定会把慢的人追上)如图:
找到环了,接下来该确定环的入口了,这里用到了大量的数学知识,如图:
慢指针走的路程为,快指针走的路程为,相信大家对快指针走的路程没有问题,那么对于慢指针大家可能有些疑惑为什么是,刚开始我也有些疑惑,下面看代码随想录中的这几张图片:
当slow进入环时,fast也一定之前就进入了环,假设slow在环入口时,fast也在环入口
那么可以发现slow在环内的第一圈末尾时与fast相遇,假设slow进入环时,fast不在入口:
那么一定是fast先到环入口3,说明在此之前slow与fast以及相遇(一定会相遇,由物理知识可得,fast相对于slow一次走一个节点)。
OK,说完为什么相遇时slow走了,那么该说如何求解入口了,由于fast走的路程是slow的二倍:所以可得,化简为,变形为.由此可以得出,当快指针从相遇节点按一个一个节点走时,slow从head按一个一个节点走时,会在环入口相遇:
所以代码如下
type T=ListNode | null
function detectCycle(head: ListNode | null): ListNode | null {
let fast:T=head
let slow:T=head
while(fast&&fast.next){ //求是否有环
slow=slow!.next
fast=fast.next.next
if(slow==fast){ //求环入口
slow=head
while(slow!=fast){
slow=slow!.next
fast=fast!.next
}
return slow
}
}
return null
};
思考
这道题对我来说很妙,用到了非常多的数学思想,对于双指针的用法又有了新的理解。只能说还是太年轻了.判断是否有环和环的入口的寻找看了文章好久才能梳理完,这道题得多加练习。
总结
今天四道题,花费时间4小时,前两道较为轻松,第三道虽然做出来了但是有个重要的条件没想到,第四题也做出来了,但是文章的思路确实妙,完全没想到文章的思路.