24. 两两交换链表中的节点
题目:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
思路:
- 首先创建一个虚拟头节点dummyNode值为0
- 声明一个指针cur指向真实头节点,即dummyNode.next
- 声明一个临时指针tmp
- 声明一个指针prev指向真实头节点,记录cur移动过程中的前一个节点
- 判断当前链表长度如果小于2,则返回整个链表
- 声明判断条件为cur的值的循环
- 首先判断如果cur.next为null则跳出循环,用于匹配奇数链表的最后一个值
- 将cur.next赋值给临时指针tmp
- 将cur.next指向tmp.next
- 将tmp.next指向cur
- 如果虚拟头节点的next是cur,说明此时是第一次交换,要将虚拟头节点额外处理,指向目前tmp的节点
- 如果prev.next此时指向cur,说明不是第一次交换,每次交换后cur.next上一次指向的节点此次交换后位置更换,所以要将prev.next指向tmp,cur赋值给prev
- 最后将cur向后移一位
- 返回虚拟头节点的next,即变换后的链表。
代码:
/**
* @param {ListNode} head
* @return {ListNode}
*/
var swapPairs = function(head) {
let dummyNode = new ListNode(0,head)
let cur = dummyNode.next
if (!cur||!cur.next) return head
let tmp,prev = cur
while (cur) {
if (!cur.next) break
tmp = cur.next
cur.next = tmp.next
tmp.next = cur
if (dummyNode.next===cur) {
dummyNode.next = tmp
}
if (prev.next === cur) {
prev.next =tmp
prev = cur
}
cur = cur.next
}
return dummyNode.next
};
优化:
看了代码随想录发现自己思路没问题,但是代码逻辑写的非常复杂冗余,理解了优化后的代码后自己再写一遍。
代码:
/**
* @param {ListNode} head
* @return {ListNode}
*/
var swapPairs = function(head) {
let dummyNode = new ListNode(0,head)
let cur = dummyNode
while (cur.next&&cur.next.next) {
let tmp = cur.next.next,pre = cur.next
pre.next = tmp.next
tmp.next = pre
cur.next = tmp
cur = pre
}
return dummyNode.next
};
总结:
这道题第一次提交是我独立写出来的,虽然逻辑复杂,但是功能实现,重要的是进一步简化逻辑的思想。
19. 删除链表的倒数第 N 个结点
题目:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
思路:
- 声明一个虚拟头节点
- 声明一个指针cur指向虚拟头节点
- 进入判断条件为cur.next不为null的循环
- 设置一个索引index保存n的大小
- 设置一个临时指针tmp指向cur.next
- 进入判断条件为index大于0的循环,每次判断后index--,目的是判断当前的cur.next是否是我们要删除的值
- 将tmp指向tmp.next
- 判断如果此时tmp为null,并且index为0,此时这cur.next是我们要删除的值,直接将其移除,返回操作后的链表
- cur指向cur.next
代码:
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
let dummyNode = new ListNode(0,head)
let cur = dummyNode
while (cur.next) {
let index = n
let tmp = cur.next
while (index-- >0) {
tmp = tmp.next
if (!tmp&&!index) {
cur.next = cur.next.next
return dummyNode.next
}
}
cur = cur.next
}
};
优化:
看了代码随想录的双指针解法我觉得自己就是傻呗..其实我自己的思想从某种程度上来说也是一种双指针?ok学习了思路以后再重新写一遍。
代码:
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
let slow,fast
let dummyNode = new ListNode(0,head)
slow = fast = dummyNode
while (n-- >=0){
fast = fast.next
}
while (true) {
if (!fast) {
slow.next = slow.next.next
return dummyNode.next
}
slow = slow.next
fast = fast.next
}
};
总结:
虽然自己思考这个解法逻辑,思考了很久很久,但是这次我思考出来的逻辑并不太复杂,总归也是自己做出来了,但是看到优化后的双指针解法以后一下就明白了,而且这个思路非常清晰,希望自己未来也可以看到题目就有非常清晰的思路。
面试题02.07. 链表相交
题目:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
![[Pasted image 20230610170855.png]]
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
思路:
设置两个循环,进行比较,时间复杂度On2
- 判断如果A或者B链表为null,直接返回null
- 声明一个指针curA指向链表A
- 进入判断条件为curA的循环
- 声明一个指针curB指向链表B
- 进入判断条件为curB的循环
- 判断如果curA与curB在目前这个节点相交,则返回任意一个链表的当前节点
- curB指向curB.next
- curA指向curA.next
- 最后返回null
代码:
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
if (!headA||!headB) return null
let curA = headA
while (curA) {
let curB = headB
while (curB) {
if (curB===curA) {
return curA
}
curB = curB.next
}
curA = curA.next
}
return null
};
优化:
看了代码随想录的思路,绝了,吗的我怎么想不到,我还一直在想时间复杂度On怎么用双指针的方式,思路是把较短的链表与较长的链表末尾对齐,然后就可以从短链表的开头进行同步比较。
代码:
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
if (!headA||!headB) return null
let curA = headA, curB = headB,lenA = 0,lenB = 0
while (curA.next) {
lenA++
curA = curA.next
}
curA = headA
while (curB.next) {
lenB++
curB = curB.next
}
curB = headB
if (lenA >= lenB) {
let dev = lenA-lenB
return getRes(curA,curB,dev)
}else {
let dev = lenB-lenA
return getRes(curB,curA,dev)
}
return null
};
const getRes = (curA,curB,dev) =>{
while (dev-- >0) {
curA = curA.next
}
while (true) {
if (curA === curB) {
return curA
}
curA = curA.next
curB = curB.next
}
}
总结:
自己的编程思维还需要多培养。
142.环形链表II
题目:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
思路:
想不明白。
代码:
写不出来。
优化:
看了代码随想录的思路:
- 设置两个快慢指针,快指针每次走两步,慢指针每次走一步,设置一个循环,如果两个指针相遇,说明一定存在环形链表。
- 如果存在环形链表,接下来的任务就是找到环形链表的入口,这里涉及到数学问题。
- 设链表开头到环形入口的距离x,慢指针在环形内走过的距离为y,慢指针距离下次走到环形入口的距离为z,那么慢指针走过的路程为x+y,快指针走过的路程为x+y+n(y+z)
- 快指针的速度是慢指针的两倍,时间相等,所以(x+y)*2 = x+y+n(y+z)
- 化简:x=(n-1)(y+z) + z
- 无论n为几,最后相遇点一定是环形链表的入口
代码:
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
let slow,fast
slow = fast = head
while (fast&&fast.next) {
slow = slow.next
fast = fast.next.next
if (slow === fast) {
let indexS = slow,indexF = head
while (indexF!==indexS) {
indexF = indexF.next
indexS = indexS.next
}
return indexF
}
}
return null
};
总结:
看解析之前感觉很难,两个关键点——判断是否存在环形,环形入口在哪完全想不明白,看完解析以后又觉得就这?希望自己不仅学习这个题解之后也能学到这种解题思路。
Day4总结
今天做的题是在昨天初步认识链表的基础上去完成的,在昨天对链表一窍不通的情况下去看题解做题后,今天对链表已经有了认识,前三题都是在自己没看题解的情况下做出来,无论是否是最优解,这都是进步的表现,但是缺少的还是数据结构的思想,希望自己再接再厉。