【问题标题】:Is there a more elegant solution to getting the inverse of an array of ranges?是否有更优雅的解决方案来获得范围数组的倒数?
【发布时间】:2019-04-10 13:35:13
【问题描述】:

让我们想象一下:

你有一条从 1 开始到 10000 结束的行。

你会得到一个范围数组,比如[{ start: 5, end: 10 }, { start: 15, end: 25 }]

给定一个范围数组,求逆。

对于上面的例子,相反的是[{ start: 1, end: 4 }, { start: 11, end: 14 }, { start: 26, end: 10000 }]

请注意,倒数基本上是我们线上的每个其他范围。

以下是我目前的解决方案...是否有更优雅的解决方案,不必明确处理边缘情况?

请注意,在我的代码范围中是命名区域。

const inverseRegions = (regions) => {

  // If no regions, the inverse is the entire line.
  if (regions.length === 0) { 
    return [{ start: 1, end: 10000 }] 
  }

  let result = []

  // If the first region doesn't start at the beginning of the line
  // we need to account for the region from the 1 to the start of
  // first region
  if (regions[0].start !== 1) {
    result.push({
      start: 1,
      end: regions[0].start - 1
    })
  }

  for (let i = 1; i < regions.length; i++) {
    const previousRegion = regions[i-1]
    const region = regions[i]

    result.push({
      start: previousRegion.end + 1,
      end: region.start - 1
    })
  }

  // If the last region doesn't end at the end of the line
  // we need to account for the region from the end of the last
  // region to 10000
  if (regions[regions.length - 1].end !== 10000) {
    result.push({
      start: regions[regions.length - 1].end + 1,
      end: 10000
    })
  }

  return result
}

预期结果:

inverseRegions([]) 
  => [ { start: 1, end: 10000 } ]

inverseRegions([{ start: 1, end: 10 }, { start: 15, end: 20 }]) 
  => [ { start: 11, end: 14 }, 
       { start: 21, end: 10000 } ]

inverseRegions([{ start: 5, end: 10 }, { start: 12, end: 60 }, { start: 66, end: 10000 }]) 
  => [ { start: 1, end: 4 },
       { start: 11, end: 11 },
       { start: 61, end: 65 } ]

inverseRegions([{ start: 8, end: 12 }, { start: 16, end: 20 }, { start: 29, end: 51 }]) 
  => [ { start: 1, end: 7 },
       { start: 13, end: 15 },
       { start: 21, end: 28 },
       { start: 52, end: 10000 } ]

【问题讨论】:

  • 为什么不[1,5] 以及为什么[1,4] ?因为然后范围 [4,5] 被跳过。还是我错过了什么?
  • @vivek_23 生成区域的方式是,如果[4,5] 存在,它将成为[5, 10] 的一部分,而[5, 10] 在传递给函数时将是[4, 10]。这就是为什么它会是[1, 4]。另一种极端情况是,如果您有[10, 13][15, 17],则可以有一个单点范围,如[14, 14]
  • 但是,在我们没有覆盖的线上总会有一个缺口。
  • 我明白你在说什么。在我的问题中,现在生成的东西是有效的,在一般情况下,你是对的。我们正在留下一个空白。

标签: javascript arrays algorithm range


【解决方案1】:

您可以使用 reduce 初始化一个包含整个区域的累加器,然后在遇到新范围时拆分最后一个区域:

function inverseRegions(ranges) {
  return ranges.reduce((acc, curr) => {
    let prev = acc.pop();
    if (curr.start > prev.start) 
      acc.push({start: prev.start, end: curr.start - 1})
    if (prev.end > curr.end)
      acc.push({start: curr.end + 1, end: prev.end});
    return acc;
  }, [{start: 1, end: 10000}])
}


console.log(inverseRegions([{ start: 5, end: 10 }, { start: 15, end: 25 }]));
console.log(inverseRegions([]));
console.log(inverseRegions([{ start: 1, end: 10 }, { start: 15, end: 20 }]));
console.log(inverseRegions([{ start: 5, end: 10 }, { start: 12, end: 60 }, { start: 66, end: 10000 }])); 
console.log(inverseRegions([{ start: 8, end: 12 }, { start: 16, end: 20 }, { start: 29, end: 51 }]));

这当然假设您的间隔是排序的。

【讨论】:

  • 我有一种非常好的感觉,我可以使用 reduce 来完成它,我认为这就是方法!会花更多时间来回答不同的答案,但这看起来很棒。
  • 你可以进一步重构它:在你做curr.start - prev.start &gt; 0的地方,你可以只做curr.start &gt; prev.start
  • 你的解决方案肯定比我的更优雅,我非常喜欢。这绝对是一个微优化,但我想知道您的解决方案相对于我的解决方案做了多少次推送/弹出。如果我的函数得到n,那么你最多会有n+1 推送。在您的解决方案中,我认为您将拥有 n pops + 2n pushes,即 3n 总操作数。我想你无法通过减速器解决方案来避免这种情况。不过看起来真的很不错:)
  • @David 另请注意,您将unshift 用于边缘情况,我认为在大多数 JavaScript 实现中是 O(n) 操作(与 @987654333 不同@ 是 O(1) - 请参阅 stackoverflow.com/questions/12250697/…)。这会将您的数组操作带到 2n 左右。
  • 是的,你是对的。我想了想。我可以使用push 操作将它移到for 循环的前面。我会编辑我的。一天结束,我很可能会和你一起去。实际上,n 操作和3n 之间的差异根本不会引起注意。
猜你喜欢
  • 2015-07-03
  • 2023-03-30
  • 2018-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多