24. 两两交换链表中的节点
题目连接:24. 两两交换链表中的节点 - 力扣(Leetcode)
遇到难点
暂无,但是对于递归的方法无法想到
题解
1.迭代法
理解题目意思后,其实就可以简单的理解为几个数交换位置,由于链表的特殊性质,在添加节点的时候是不需要零时创建变量存储的,但是在这题中,明显需要。其次,对于头部节点的区别,最好的方法就是创建一个虚拟节点从而不用额外考虑头部节点
var swapPairs = function(head){
let dummyHead = new ListNode(0,head)
let cur = dummyHead
while(cur.next != null && cur.next.next != null){
let node1 = cur.next
let node2 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = node1
cur.next.next.next = node2
cur = cur.next.next
}
return dummyHead.next
}
时间复杂度:O(n)
空间复杂度:O(1)
需要注意的是,在不清晰每一条链如何断开连接的时候,应该一步一步画出图像,思路自然而然就清晰了
2.递归法
递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。
果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。
var swapPairs = function(head) {
if (head === null|| head.next === null) {
return head;
}
const newHead = head.next;
head.next = swapPairs(newHead.next);
newHead.next = head;
return newHead;
}
时间复杂度:O(n)
空间复杂度:O(n)(递归栈的空间)
19.删除链表的倒数第N个节点
题目连接:19. 删除链表的倒数第 N 个结点 - 力扣(Leetcode)
遇到难点
第一眼看,删除第n个节点,这个真的简单,然后再一看发现是倒数第n个节点,没有很好的思路如何找到这个节点
题解
可以换个角度理解这个倒数n
,比如以倒数第一个节点为开始,距离其为n
的节点;以倒数第二个节点未开始,距离其为n
的节点 …
不难发现,只要我们设置两个指针,保证其的距离为n时,当快指针到结尾的时候,慢指针指的地方就是倒数第n个节点
var removeNthFromEnd = function(head, n) {
let dummyHead = new ListNode(0,head)
let slow = dummyHead
let fast = dummyHead
while(n -- && fast != null){
fast = fast.next
}
fast = fast.next
while(fast != null){
slow = slow.next
fast = fast.next
}
slow.next = slow.next.next
return dummyHead.next
}
时间复杂度:O(n)
空间复杂度:O(1)
这里需要注意的是,由于是要删除链表的某一个节点,因此应该找到他的上一个节点,将next
指向被删节点的下一个节点即next.next
,因此fast
应该比slow
的快n+1
个节点
02.07. 链表相交
题目连接: 面试题 02.07. 链表相交 - 力扣(Leetcode)
遇到难点
嗯。。。。其实就是完全没得思路,不知道从哪里开始模拟,开始想到用哈希表存,如果一样的话,那是了,但是感觉十分的丑陋(但是确实后来发现思路没啥问题)
题解
1.哈希表
var getIntersectionNode = function(headA, headB) {
const visited = new Set();
let temp = headA;
while (temp !== null) {
visited.add(temp);
temp = temp.next;
}
temp = headB;
while (temp !== null) {
if (visited.has(temp)) {
return temp;
}
temp = temp.next;
}
return null;
}
时间复杂度:O(n+m)
空间复杂度:O(m)
2.双指针法
首先观察题目特性,如果有公共交点,那么两个链表的结尾一定是一模一样的;其次如果两个链表一样长,我们可以以此一个节点一个节点的判断,然后比较是否相同,但是问题再两个链表不一样长,但是基于第一条性质,我们发现长链表前面几个节点一定不会是焦点,如果我们把这些节点忽略,不就能使得两个链表一样长了?
话不多说见图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UzaKhyl8-1673790173789)(https://code-thinking.cdn.bcebos.com/pics/%E9%9D%A2%E8%AF%95%E9%A2%9802.07.%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4_2.png)]
那么需要做的就是,求出两个的长度,求出插值,给右侧对其,然后一起遍历
var getIntersectionNode = function(headA, headB) {
let lenA = getLength(headA)
let lenB = getLength(headB)
// 保证A的长度大于B
if(lenA < lenB){
[lenA, lenB] = [lenB, lenA]; // 这个分号必须要有,不然会被默认为 [lenA, lenB] = [headB, headA]
[headA, headB] = [headB, headA]
}
let i = lenA - lenB
let curA = headA
let curB = headB
while(i --){
curA = curA.next
}
while(curA != null){
if(curA == curB){
return curA
}
curA = curA.next
curB = curB.next
}
return null
}
var getLength = function(head){
let n = 0
while(head != null){
head = head.next
n ++
}
return n
}
时间复杂度:O(n+m)
空间复杂度:O(1)
需要注意的是,对于我这种javascript
不带分号的选手而言,需要特别主要,有些时候必须使用;
- 小括号开头的语句, 前面需要分号
- 中括号开头的语句, 前面需要分号
其次,对于题目中的判断相交节点应该注意是指针相同,而且不是数值相同,
比如说测试用例一中
listA = [4, 1, 8, 4, 5]
listB = [5, 0, 1, 8, 4,5]
判定值为8
而不是1
,原因就是答案在判定是是否指向同一个节点,对于节点值为1
的节点判断出来他们两个同时指向8
,因此相交节点为8
142.环形链表II
题目连接: 142. 环形链表 II - 力扣(Leetcode)
遇到难点
思考题目时,单纯从编码解答模拟的角度思考了,对于这样的题目没有运用一定的数学推理去做,从而无法找到关键公式
题解
1. 分析过程
这个题目可以理解成初中(小学?)的跑步追击问题,大概就是小明跑的快,小红跑的慢那什么时候追上。
具体到题目而言,我们需要的是找到一个入口的位置,那么可以分析成两点去解析
- 有没有环?
- 环的入口在哪?
接下来以此用数学逻辑解决
1.有没有环
这里其实就是刚刚说的追击问题,如果两个人跑步,一快一慢,一条直线上肯定不会相遇,如果是一段直线+一段圈呢?这不就相遇了?
因此,我们可以设置两个指针fast
的速度是slow
的两倍,如果能有相遇的位置,那就是有环,而且相遇点一定在环内,看个动态图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tsAxRIx2-1673790173790)(https://code-thinking.cdn.bcebos.com/gifs/141.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.gif)]
2.环的入口在哪
首先如图:
对于slow
的路程为 x + y
对于fast
的路程为x + n(y + z)
,其中n
为圈数,而且肯定有n >= 1
(想想跑步快的追慢的,追上了肯定起码多跑了一圈,除非慢的会时空穿梭(大雾))
所以可以得到等式2 (x + y) = x + n (y + z)
,由于我们需要求的是x
因此我们转化一下可得x = n (y + z) - y
再给变一下 x = (n - 1)(y + z) + z
这表明,快慢指针相遇之后,将剩下的节点走完,则可得到入口结点;因此我们可以给两个index
,当两个相等的时候判断为入口即可,具体可看代码
2.具体代码实现
var detectCycle = function(head) {
let fast = slow =head
while(fast != null && fast.next != null){
slow = slow.next
fast = fast.next.next
if(slow == fast){
let index1 = head
let index2 = fast
while(index1 != index2){
index1 = index1.next
index2 = index2.next
}
return index2
}
}
return null
}
时间复杂度:O(n)
空间复杂度:O(1)
今日总结
- 学会将题目细致化为几个问题,然后一一解决判断
- 多画图多动手
- 当涉及到节点的增删的时候,可以添加一个虚拟节点来保证不用特别区别头部节点
- 链表题目经常会报空指针异常,因此在判断循环条件的时候,应该格外注意,比如为了不使
node.next.next
空指针,则应该判断node.next != null