TL;DR
写两个帮助函数来创建链表。系列目录见 。
需求
写两个方法 push
和 buildList
来初始化链表。尝试在 buildList
中使用 push
。下面的例子中我用 a -> b -> c
来表示链表,这是为了书写方便,并不是 JavaScript 的有效语法。
let chained = nullchained = push(chained, 3)chained = push(chained, 2)chained = push(chained, 1)push(chained, 8) === 8 -> 1 -> 2 -> 3 -> null
push
用于把一个节点插入到链表的头部。它接受两个参数 head 和 data ,head 可以是一个节点对象或者 null
。这个方法应该始终返回一个新的链表。
buildList
接收一个数组为参数,创建对应的链表。
buildList([1, 2, 3]) === 1 -> 2 -> 3 -> null
定义节点对象
作为链表系列的第一课,我们需要先定义节点对象是什么样子。按照 Codewars 上的设定,一个节点对象有两个属性 data 和 next 。data 是这个节点的值,next 是下一个节点的引用。这是默认的类模板。
function Node(data) { this.data = data this.next = null}
push
这是 push
的基本实现:
function push(head, data) { const node = new Node(data) if (head) { node.next = head return node } else { return node }}
我更倾向于修改一下 Node 的构造函数,把 next 也当成参数,并且加上默认值,这会让后面的事情简化很多:
function Node(data = null, next = null) { this.data = data this.next = next}
新的 push
实现:
function push(head, data) { return new Node(head, data)}
buildList
递归版本
这个函数非常适合用递归实现。这是递归的版本:
function buildList(array) { if (!array || !array.length) return null const data = array.shift() return push(buildList(array), data)}
递归的思路是,把大的复杂的操作逐步分解成小的操作,直到分解成最基本的情况。拿这个例子解释,给定数组 [1, 2, 3]
,递归的实现思路是逐步往链表头部插入数据 3,2,1 ,一共三轮。第一轮相当于 push(someList, 3)
。这个 someList
是什么呢,其实就是 buildList([1, 2])
的返回值。以此类推:
第一轮
push(buildList([1, 2]), 3)
第二轮
push(buildList([1]), 2)
第三轮
push(buildList([]), 3)
到第三轮就已经是最基本的情况了,数组为空,这时返回 null
代表空节点。
循环版本
依照上面的思路,循环也很容易实现,只要反向遍历数组就行。因为循环已经考虑了数组为空的情况,这里就不用进行边界判断了。
function buildListV2(array) { let list = null for (let i = array.length - 1; i >= 0; i--) { list = push(list, array[i]) } return list}
One-liner
结合循环版本的思路和 JavaScript 的数组迭代器,我们可以得出一个 one-liner 版本。
function buildListV3(array) { return (array || []).reduceRight(push, null)}
这个就不解释了,留给各位自己思考下吧。