这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
本文题目全部来自 LeetCode
使用
Typescript
本篇文章全部收藏于专栏 3周攻克数据结构-LeetCode
本文所有代码和解题步骤将放置 GitHub仓库
DAY7
1. 环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
单链表的构造方法:👇🏻
class ListNode {
val: number
next: ListNode | null
constructor(val?: number, next?: ListNode | null) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
}
方法1:哈希表查询
- 建一个哈希表储存已经处理过的值
- 遍历链表;当前值内链表包含则表示有换
function hasCycle(head: ListNode | null): boolean {
// 建表记录
const MAP = []
while(head) {
// 存即有环
if(MAP.includes(head)){
return true
}
// 判断过的计入哈希表
MAP.push(head)
// 判断下一个 链路
head = head.next
}
// 无环
return false
};
方法1-1:哈希表延升法:链表记录法,也叫污链表法
找过的链路增加一个
flag
标识符遇到标识符说明有环
- 缺点:会改变原有数据结构
- 优点:不需要单独展位一个内存
function hasCycle(head: any): boolean {
while (head) {
if(head.flag) {
return true
}
head.flag = true;
head = head.next;
}
return false
};
方法2:快慢指针
快慢指针和我们之前做的
双指针
的题原理大致是一样的:
- 一个是用2个指针缩短路径,一个是用2个指针减少占用内存(对比哈希表解法)
- 快慢指针的原理很简单就是遍历一次链表,快指针比慢指针快一步,指针重叠则说明有环
function hasCycle(head: ListNode | null): boolean {
if (!head) return false
// 初始化 快指针和慢指针
let slow_p = head
let fast_p = head
while (fast_p.next !== null && fast_p.next.next !== null) {
// 快指针比慢指针快一步
slow_p = slow_p.next
fast_p = fast_p.next.next
// 如果快指针追上了慢指针则证明有环
if (slow_p === fast_p) return true
}
return false
};
方法3:JS语言特性
JSON.stringify
方法会自动检测传入的对象是否为环,如果JSON.stringify
成功执行,那说明传入的对象一定不是环
function hasCycle(head: ListNode | null): boolean {
try {
JSON.stringify(head)
return false
} catch {
return true
}
};
2. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
方法1:递归
function mergeTwoLists(l1: ListNode | null, l2: ListNode | null): ListNode | null {
// 其中一个空了证明排序 排完了
if (!l1 || !l2) {
return l1 || l2
}
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
};
方法2:迭代
function mergeTwoLists(l1: ListNode | null, l2: ListNode | null): ListNode | null {
// 已知范围最小的是 0,nwe 一个出来做比较
const newList = new ListNode(0);
let cur = newList;
while (l1 && l2) {
if (l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
cur.next = l1 ? l1 : l2;
return newList.next;
};
3. 移除链表元素
方法1:递归
function removeElements(head: ListNode | null, val: number): ListNode | null {
if (head === null) return head
head.next = removeElements(head.next, val);
// 满足条件跳过当前 链表
return head.val === val ? head.next : head;
};
方法2:迭代
function removeElements(head: ListNode | null, val: number): ListNode | null {
const preHead = new ListNode(0);
preHead.next = head;
let cur = preHead;
while (cur.next !== null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return preHead.next;
};
总结
链表数据结构,大都是使用
递归
来处理