拒绝摆烂ヾ(◍°∇°◍)ノ゙
从今天开始(2023/02/12
),定一个小目标,先刷个 300
道 Leetcode
题目(之前刷的不计入)。
当然作为一个小前端,我选择的语言是 TS
,而且刷的题目的难度会偏中等一些,大概按照 简单3
中等6
困难1
这样的题型分布吧。嗯,目前是这么打算的。
本题 Github 地址:因为比较喜欢 vscode
的界面,而且方便调试,所以 AC
完就顺便提到 github
了,也算做一个记录吧。
本篇的题目是这个系列的第 NO.6
、NO.7
和 NO.8
道,分别是 Leetcode
上第 203
道题 移除链表元素, 第 145
道题 二叉树的后序遍历 以及 第 234
道 回文链表,难度都为 简单。
我们开始吧,Here We Go~
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入: head = [1,2,6,3,4,5,6], val = 6 输出: [1,2,3,4,5]
示例 2:
输入: head = [], val = 1 输出: []
示例 3:
输入: head = [7,7,7,7], val = 7 输出: []
提示:
[0, 104]
内1 <= Node.val <= 50
0 <= val <= 50
思路:
previous
(上一个节点)tsfunction removeElements(head: ListNode | null, val: number): ListNode | null {
let ret: ListNode | null = head;
let previous: ListNode | null = null;
let current: ListNode | null = head;
// 1. 遍历链表
while (current) {
if(current.val === val) {
// 2. 遍历到值相同时, 删除节点
if(previous === null) {
// 1. 要删除的节点位于头节点
current = current.next
ret = current
} else {
// 2. 要删除的节点不位于头节点
previous.next = previous?.next?.next ?? null
current = previous.next
}
} else {
// 3. 遍历到值不相同时,继续遍历,记录 previous(上一个节点)
previous = current
current = current.next
}
}
return ret;
}
复杂度分析:
O(n)
O(1)
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入: head = [1,2,3,4,5] 输出: [5,4,3,2,1]
示例 2:
输入: head = [1,2] 输出: [2,1]
示例 3:
输入: head = [] 输出: []
提示:
[0, 5000]
-5000 <= Node.val <= 5000
进阶: 链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
举个例子,我们现在要将 1 2 3 4 5 6
反转成 6 5 4 3 2 1
,我们的步骤应该是:
1 -> 2
变为 1 <- 2
prev
curr
next
三个变量分别只想上一个节点、当前节点、下一个节点prev
为 null
,curr
为 1
,next
为 2
1.next
指向 prev
prev
赋值为 1
curr
赋值为 2
进行下一个循环tsfunction reverseList(head: ListNode | null): ListNode | null {
let prev: ListNode | null = null;
let curr: ListNode | null = head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev
}
复杂度分析
O(n)
,其中 n
是链表的长度。需要遍历链表一次。O(1)
。递归的写法稍微有点难理解
举个例子,我们现在要将 1 2 3 4 5 6
反转成 6 5 4 3 2 1
,按照递归的方法我们的步骤应该是:
5 -> 6
变为 6 -> 5
,再拿到 4 -> 5
变为 5 -> 4
再拿到 3 -> 4
变为 4 -> 3
........直到拿到 1 -> 2
变为 2 -> 1
5 -> 6
变为 6 -> 5
head
为 5
, newHead
为 6
head.next.next = 5; 5.next = null
的方法将 5 -> 6 变为 6 -> 5
注意:在第 4
步这里的head.next.next = 5
不能变为 newHead.next = 5
,因为 newHead
是最终返回的链表头节点,在这一步中你虽然将 6 指向了 5
,但是在下一个循环中 head
为 4
,这样赋值就会变成 6 指向 4
tsfunction reverseList(head: ListNode | null): ListNode | null {
// 1. 判断节点为 null,或者只要一个节点,那么直接返回即可
if (head === null || head.next === null) {
return head;
}
const newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
复杂度分析
时间复杂度:O(n)
,其中 n
是链表的长度。需要对链表的每个节点进行反转操作。
空间复杂度:O(n)
,其中 n
是链表的长度。空间复杂度主要取决于递归调用的栈空间,最多为 n
层。
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
示例 1:
输入: head = [1,2,2,1] 输出: true
示例 2:
输入: head = [1,2] 输出: false
提示:
[1, 105]
内0 <= Node.val <= 9
这种是最简单的方法
循环遍历链表,将链表的节点赋值到数组中,将数组转换为字符串比较,接下来就是验证回文串了
tsfunction isPalindrome(head: ListNode | null): boolean {
const arr: number[] = [];
while (head) {
arr.push(head.val);
head = head.next;
}
return arr.join("") === arr.reverse().join("");
}
复杂度分析:
O(n)
O(n)
避免使用 O(n)
额外空间的方法就是改变输入。
避免使用 O(n)
额外空间的方法就是改变输入。
我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。
该方法虽然可以将空间复杂度降到 O(1)
,但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。
整个流程可以分为以下五个步骤:
tsfunction reverseList(head: ListNode | null): ListNode | null {
let prev: ListNode | null = null;
let curr: ListNode | null = head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
function endOfFirstHalf(head: ListNode | null): ListNode | null {
if (!head) return null;
let slow: ListNode | null = head;
let fast: ListNode | null = head;
while (fast.next && fast?.next.next) {
fast = fast.next?.next ?? null;
slow = slow!.next;
}
return slow;
}
function isPalindrome(head: ListNode | null): boolean {
if (head == null) return true;
// 1. 找到前半部分链表的尾节点并反转后半部分链表
const firstHalfEnd = endOfFirstHalf(head);
const secondHalfStart = reverseList(firstHalfEnd!.next);
// 2. 判断是否回文
let p1: ListNode | null = head;
let p2: ListNode | null = secondHalfStart;
while (p1 && p2) {
if (p1.val !== p2.val) return false;
p1 = p1.next;
p2 = p2.next;
}
// 还原链表并返回结果
firstHalfEnd!.next = reverseList(secondHalfStart);
return true;
}
本文作者:叶继伟
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!