本人是个算法渣渣,该内容为个人在练习算法中的刷题记录。记录了个人对于题解的一些理解总结。如果错误欢迎指正!
1.反转链表
题目来源:LeetCode 206. 反转链表
描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
进阶: 链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
迭代解法
-
解题思路:
- 先定义两个指针
pre
、cur
分别表示前置节点和当前节点,要进行链表反转的话,只要实现所有节点的的 next 指向其前置节点即可 - 设置
pre
初始值为null
,cur
初始值为head
,因为反转后,head
节点应成为链表末端节点,其next
值为null
。因此这样设置两个指针 - 遍历所有节点,将所有
cur
节点的next
指向pre
,之后让cur
和pre
顺着链表原来的方向往下遍历,直到所有节点都被遍历结束 - 当
cur
为null
时,意味着已经遍历到链表末端,此时的pre
成为了新链表的其实端,此时结束循环,返回 pre 即可
- 先定义两个指针
-
注意
- 在遍历过程中,因为会更改
cur.next
的指向,因此要先用一个临时指针(tmp
)保存下一个节点的位置,使得原链表的遍历能够正常执行下去
- 在遍历过程中,因为会更改
-
代码:
function reverseList (head) {
let pre = null
let cur = head
while (cur) {
let tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
}
return pre
}
递归解法
参考:简单易懂 Java/C++ /Python/js 动画讲解 - 反转链表 讲解的十分细致
-
解题思路:
- 根据递归的思想,可以将链表反转问题拆解成:头节点与除头节点以外的子链表进行反转;而子链表的反转一样的可以像上述解法一样,也进行拆分。如:
1 -> 2 -> 3 -> 4 -> 5 -> null 其反转可以拆分为: | head | List | | 1 | 2 -> 3 -> 4 -> 5 -> null | 而对于其子链表List,又可以同样的进行拆分 | head | List | | 2 | 3 -> 4 -> 5 -> null | 如此往复下去即可...
- 有
递
就有归
,根据上述规则一直拆分问题,就是递
,等拆分到只剩一个节点时,就无需继续拆分,开始归
,即将拆分到无法拆分的节点返回。(这个节点也就是我们要的新链表的头节点) - 在
归
的同时,我们也要进行链表反转的操作,也就是解决上述的头节点和子链表的反转,要进行这样的反转,我们只要进行head.next.next = head;head.next=null
即可
-
注意:
- 递归的中止条件为链表只剩一个节点时结束递归,因此判断条件为
head.next === null
。但是如果在执行时,传入一个空链表,函数也应立即结束,返回head
。所以递归中止条件为head===null||head.next===null
- 递归中止返回的节点为新链表的头节点,我们应当保存下来,用于结果返回
- 递归的中止条件为链表只剩一个节点时结束递归,因此判断条件为
-
代码:
if (head === null || head.next === null) {
return head
}
const p = reverseList(head.next)
head.next.next = head
head.next = null
return p
2.删除链表的节点
题目来源:剑指 Offer 18. 删除链表的节点
描述
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 返回删除后的链表的头节点。
输入:list=[4,5,1,9],val=5
输出:[4,1,9]
解法
- 解题思路:
- 本题比较简单,只要去遍历链表,然后去找到符合条件的节点,再进行删除操作即可。即将要删除节点的前一个节点的
next
指向要删除节点的next
- 本题可以使用双指针逻辑比较清晰,这里我直接建立一个空节点指向链表头部,判断时使用下一节点的值进行比较,直接使用指针定位到要删除节点的前驱节点
p
,然后将其 next 设置为p.next.next
- 本题比较简单,只要去遍历链表,然后去找到符合条件的节点,再进行删除操作即可。即将要删除节点的前一个节点的
- 注意
代码:
var deleteNode = function (head, val) {
let dummyHead = new ListNode(undefined, head)
for (let p = dummyHead; p.next; p = p.next) {
if (p.next.val == val) {
p.next = p.next.next
break
}
}
return dummyHead.next
}
3.移除链表元素
题目来源:LeetCode 203. 移除链表元素
描述
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
迭代解法
-
解题思路:
- 迭代思路比较简单,本题与删除链表节点比较类似,遍历节点,如果遇到符合条件的节点,就直接删除。
- 迭代方法这里依旧使用空节点指向头节点,然后使用
while
循环遍历链表,同样的直接使用p
先指向我们创建的空节点,然后判断p.next.val===val
;如果相等则令p.next=p.next.next
- 最后返回我们创建节点的 next 即可
-
代码:
var removeElements = function (head, val) {
let dummyHead = new ListNode(undefined, head)
let p = dummyHead
while (p.next) {
if (p.next.val === val) {
p.next = p.next.next
} else {
p = p.next
}
}
return dummyHead.next
}
递归解法
- 解题思路:
- 链表被定义的时候就具有递归的性质,因此这题也能够使用递归来解决
- 对于链表,我们只要逐个的判断 head 的值是否等于传入的 val,如果是就删除,不是就不删除,而除了 head 意外的以 head.next 为首的子链也是以此的拿出首节点,判断是否要删除,要删除则删除,不要则返回
- 对于递归中,我们直接判断
head.val===val
如果为真,则代表要删除,如果为假,则可以直接返回该节点 归
意味着从末尾到前面,因此当节点为null
时,就不需要接着判断下去,就可以开始归
了,而归
返回的东西,应当作为上一次的head
的 next,但是我们在归
的时候,需要判断,这个值是否是要归的值,其实就是上一步里说的判断head.val === val
如果条件成立,则意味着这个节点要舍弃,那么这个节点就不是我们要归的内容。而应该是舍弃节点的下一个节点,即head.next
- 代码:
var removeElements = function (head, val) {
if (head == null) {
return head
}
head.next = removeElements(head.next, val)
return head.val === val ? head.next : head
}