用 JavaScript 完成链表操纵 - 04 Insert Nth Node

TL;DR

插进去第 N 个节点。系列目次见 前言和目次

需求

完成一个 insertNth() 要领,在链表的第 N 个索引处插进去一个新节点。

insertNth() 能够看成是 01 Push & Build List 中的 push() 函数的更通用版本。给定一个链表,一个范围在 0..length 内的索引号,和一个数据,这个函数会天生一个新的节点并插进去到指定的索引位置,并一直返回链表的头。

insertNth(1 -> 2 -> 3 -> null, 0, 7) === 7 -> 1 -> 2 -> 3 -> null)
insertNth(1 -> 2 -> 3 -> null, 1, 7) === 1 -> 7 -> 2 -> 3 -> null)
insertNth(1 -> 2 -> 3 -> null, 3, 7) === 1 -> 2 -> 3 -> 7 -> null)

假如索引号超出了链表的长度,函数应当抛出异常。

完成这个函数许可运用第一个 kata 中的 push 要领。

递归版本

让我们先回想一下 push 函数的用途,指定一个链表的头和一个数据,push 会天生一个新节点并添加到链表的头部,并返回新链表的头。比方:

push(null, 23) === 23 -> null
push(1 -> 2 -> null, 23) === 23 -> 1 -> 2 -> null

如今看看 insertNth ,假定函数要领署名是 insertNth(head, index, data) ,那末有两种状况:

假如 index === 0 ,则等同于挪用 push 。完成为 push(head, data)

假如 index !== 0 ,我们能够把下一个节点当做子链表传入 insertNth ,并让 index 减一。insertNth 的返回值一定是个链表,我们把它赋值给 head.next 就行。这就是一个递归历程。假如此次递归的 insertNth 完不成使命,它会继承递归到下一个节点,直到 index === 0 的最简朴状况,或 head 为空抛出异常(索引过大)。

完全代码完成为:

function insertNth(head, index, data) {
  if (index === 0) return push(head, data)
  if (!head) throw 'invalid argument'
  head.next = insertNth(head.next, index - 1, data)
  return head
}

轮回版本

假如能明白递归版本的 head.next = insertNth(...) ,那末轮回版本也不难完成。差别的是,在轮回中我们遍历到 index 的前一个节点,然后用 push 要领天生新节点,并赋值给前一个节点的 next 属性构成一个完全的链表。

完全代码完成以下:

function insertNthV2(head, index, data) {
  if (index === 0) return push(head, data)

  for (let node = head, idx = 0; node; node = node.next, idx++) {
    if (idx + 1 === index) {
      node.next = push(node.next, data)
      return head
    }
  }

  throw 'invalid argument'
}

这里有一个边境状况要注重。由于 insertNth 请求返回新链表的头。依据 index 是不是为 0 ,这个新链表的头多是天生的新节点,也能够就是老链表的头 。这点假如写进 for 轮回就不可避免有 if/else 的返回值推断。所以我们把 index === 0 的状况零丁拿出来放在函数顶部。这个边境状况并不是没法归入轮回中,我们下面引见的一个技能就与此有关。

轮回版本 – dummy node

在之前的几个 kata 里,我们提到轮回能够更好的包容边境状况,由于一些前提推断都能写到 for 的头部中去。但这个例子的边境状况是返回值差别:

  1. 假如 index === 0 ,返回新节点 。

  2. 假如 index !== 0 ,返回 head 。新节点会被插进去 head 以后的某个节点链条中。

怎样处理这个题目呢,我们能够在 head 前面再到场一个节点(数据恣意,平常赋值 null)。这个节点称为 dummy 节点。这样一来,不论新节点插进去到哪里,dummy.next 都能够引用到修改后的链表。

代码完成以下,注重 return 的差别。

function insertNthV3(head, index, data) {
  const dummy = push(head, null)

  for (let node = dummy, idx = 0; node; node = node.next, idx++) {
    if (idx === index) {
      node.next = push(node.next, data)
      return dummy.next
    }
  }

  throw 'invalid argument'
}

dummy 节点是许多链表操纵的经常使用技能,虽然在这个 kata 中运用 dummy 节点的代码量并没有变少,但这个技能在后续的一些庞杂 kata 中会异常有效。

参考资料

Codewars Kata
GitHub 的代码完成
GitHub 的测试

    原文作者:darkbaby123
    原文地址: https://segmentfault.com/a/1190000007800288
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞