TL;DR
把一个链表交替切分红两个,系列目次见 前言和目次 。
需求
完成一个 alternatingSplit()
函数,把一个链表切分红两个。子链表的节点应当是在父链表中交替涌现的。假如原链表是 a -> b -> a -> b -> a -> null
,则两个子链表分别为 a -> a -> a -> null
和 b -> b -> null
。
var list = 1 -> 2 -> 3 -> 4 -> 5 -> null
alternatingSplit(list).first === 1 -> 3 -> 5 -> null
alternatingSplit(list).second === 2 -> 4 -> null
为了简化效果,函数会返回一个 Context 对象来保留两个子链表,Context 构造以下所示:
function Context(first, second) {
this.first = first
this.second = second
}
假如原链表为 null
或许只要一个节点,应当抛出非常。
递归版本
代码以下:
function alternatingSplit(head) {
if (!head || !head.next) throw new Error('invalid arguments')
return new Context(split(head), split(head.next))
}
function split(head) {
const list = new Node(head.data)
if (head.next && head.next.next) list.next = split(head.next.next)
return list
}
这个解法的中心思绪在于 split
,这个要领吸收一个链表并返回一个以奇数位的节点构成的子链表。所以全部算法的解法就能够很容易地用 new Context(split(head), split(head.next))
示意。
另一个递归版本
代码以下:
function alternatingSplitV2(head) {
if (!head || !head.next) throw new Error('invalid arguments')
return new Context(...splitV2(head))
}
function splitV2(head) {
if (!head) return [null, null]
const first = new Node(head.data)
const [second, firstNext] = splitV2(head.next)
first.next = firstNext
return [first, second]
}
这里的 splitV2
的作用跟全部算法的寄义一样 — 吸收一个链表并返回交织支解的两个子链表(以数组示意)。第一个子链表的头自然是 new Node(head.data)
,第二个子链表呢?它实际上是 splitV2(head.next)
的第一个子链表(见第 4 行)。邃晓这个逻辑后就能够邃晓递归历程。
轮回版本
代码以下:
function alternatingSplitV3(head) {
if (!head || !head.next) throw new Error('invalid arguments')
const first = new Node()
const second = new Node()
const tails = [first, second]
for (let node = head, idx = 0; node; node = node.next, idx = idx ? 0 : 1) {
tails[idx].next = new Node(node.data)
tails[idx] = tails[idx].next
}
return new Context(first.next, second.next)
}
这个思绪是,先用两个变量代表子链表,然后对全部链表举行一次遍历,分别把节点交替插进去每一个子链表中。唯一须要斟酌的就是在每一个轮回体中推断节点该插进去哪一个链表。我用的是 idx
变量,在每轮轮回中把它交替设置成 0
和 1
。也有人运用持续增长的 idx
配合取余来做,比方 idx % 2
。做法有很多种,就不赘述了。
这里也用了 dummy node 的技能来简化 “推断首节点是不是为空” 的状况。关于这个技能能够看看 Insert Nth Node