2.两数相加
19.删除链表的倒数第 N 个结点
21.合并两个有序链表
23.合并 K 个升序链表
141.环形链表
142.环形链表 II
160.相交链表
876.链表的中间结点
25.K 个⼀组翻转链表
83.删除排序链表中的重复元素
92.反转链表 II
234.回⽂链表
2. 两数相加 (中等)
一次遍历计算完成,需注意:
- 构建空的头结点,不用处理一开始指针指向null的情况
- 执行加法,考虑进位的情况。新链表要记得移动。
- 两个链表不等长
- 读完两个链表后,最后一次进位仍要处理
21. 合并两个有序链表 (简单)
比上一题简单,不用创建新节点。
while循环的条件是 &&关系
23. 合并K个升序链表 (困难)
难题:JavaScript没有 小顶堆
数组reduce()方法详解
arr.reduce(callback,[initialValue])
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
console.log(arr, sum);
打印结果:
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10
index是从1开始的,第一次的prev的值是数组的第一个值。数组长度是4,但是reduce函数循环3次。
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0) //注意这里设置了初始值
console.log(arr, sum);
打印结果:
0 1 0
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10
例子index是从0开始的,第一次的prev的值是我们设置的初始值0,数组长度是4,reduce函数循环4次。
应用有两个,首先是一种排序方法「堆排序」,第二是一种很有用的数据结构「优先级队列」。
二叉堆在逻辑上其实是一种特殊的二叉树(完全二叉树),只不过存储在数组里。一般的链表二叉树,我们操作节点的指针,而在数组里,我们把数组索引作为指针。
优先级队列是基于二叉堆实现的,主要操作是插入和删除。插入是先插到最后,然后上浮到正确位置;删除是调换位置后再删除,然后下沉到正确位置。核心代码也就十行。
小顶堆代码:
代码来源于leetcode
class MinHeap {
constructor() {
this.heap = [];
}
// 交换节点位置
swap(i1, i2) {
[this.heap[i1], this.heap[i2]] = [this.heap[i2], this.heap[i1]];
}
// 获得父节点
getParentIndex(i) {
return Math.floor((i - 1) / 2);
// return (i - 1) >> 1;
}
// 获得左节点
getleftIndex(i) {
return 2 * i + 1;
}
// 获得右节点
getrightIndex(i) {
return 2 * i + 2;
}
// 上移
shiftUp(index) {
if (index == 0) return;
const parentIndex = this.getParentIndex(index);
if (this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val) {
this.swap(parentIndex, index);
this.shiftUp(parentIndex);
}
}
// 下移
shiftDown(index) {
let leftIndex = this.getleftIndex(index);
let rightIndex = this.getrightIndex(index);
if (this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[leftIndex].val) {
leftIndex = rightIndex
}
if (this.heap[leftIndex] && this.heap[leftIndex].val < this.heap[index].val) {
this.swap(leftIndex, index);
this.shiftDown(leftIndex);
}
// if (this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val) {
// this.swap(rightIndex, index);
// this.shiftDown(rightIndex);
// }
}
// 插入
insert(value) {
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
// 删除堆顶
// 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
pop() {
if(this.size() == 1) return this.heap.shift()
const top = this.heap[0]
// pop()方法删除数组最后一个元素并返回,赋值给堆顶
this.heap[0] = this.heap.pop();
// 对堆顶重新排序
this.shiftDown(0);
return top
}
// 获取堆顶
peek() {
return this.heap[0];
}
// 获取堆的大小
size() {
return this.heap.length;
}
}
最后该题代码:
var mergeKLists = function(lists) {
let head = p = new ListNode(0)
const heap = new MinHeap()
// 插入k个升序链表的头部节点
for (let k of lists) {
if(k) heap.insert(k)
}
// 不断的地比较最小堆中k个节点的大小
while (heap.size()) {
const val = heap.pop()
p.next = val
p = p.next
if (val.next) heap.insert(val.next)
}
return head.next;
};
160. 相交链表 (简单)
这题看一幅图就明白了,重点在于怎么简洁实现:一个while循环可解决,条件是p1 == p2
细节: 这里把最后None也当成节点
不相交的情况设A链表长度是a B链表长度是b 最后a+b = b+a 所以一定是一起走到None这个节点。可以理解为两条链表最后都指向了同一个 null (None)节点,代替了不相交的特殊情况。 非常的巧妙。
var getIntersectionNode = function(headA, headB) {
let p1 = headA, p2 = headB
while (p1 != p2) {
if(p1) p1 = p1.next;
else p1 = headB;
if(p2) p2 = p2.next
else p2 = headA
}
return p1;
};
方法二:类似找倒数第K个节点,因为如果有相交节点,最后肯定是一起走的(相同长度)。比较A和B的长度,使两条链表走相同的路程。
既然「寻找两条链表的交点」的核心在于让 p1 和 p2 两个指针能够同时到达相交节点 c1,那么可以通过预先计算两条链表的长度来做到这一点,
var getIntersectionNode = function(headA, headB) {
let lenA = 0, lenB = 0;
for (let p1 = headA; p1 != null; p1 = p1.next) lenA++;
for (let p2 = headB; p2 != null; p2 = p2.next) lenB++;
p1 = headA, p2 = headB;
if (lenA > lenB) {
for (let i = 0; i < lenA - lenB; i++) p1 = p1.next;
} else {
for (let i = 0; i < lenB - lenA; i++) p2 = p2.next;
}
// 看两个指针是否会相同,p1 == p2 时有两种情况:
// 1、要么是两条链表不相交,他俩同时走到尾部空指针
// 2、要么是两条链表相交,他俩走到两条链表的相交点
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
};