【问题标题】:JavaScript algorithms: how do I find all local max in an arrayJavaScript算法:如何在数组中找到所有局部最大值
【发布时间】:2021-09-29 23:51:43
【问题描述】:

类似于这个 leetcode 问题https://leetcode.com/problems/find-peak-element/,但返回的结果应该是所有峰值或局部最大值的索引数组

例如

给定这样的数组[1,2,1,3,5,6,4],我需要返回[2, 5]

如果我们只返回一个峰值数,我们可以使用二分查找一次消除一半的数组。这是我的解决方案

var findPeakElement = function(nums) {
    let left = 0, right = nums.length - 1
    
    while(left < right) {
        const mid = Math.floor((left + right) / 2)
        if(nums[mid] > nums[mid + 1]) {
            right = mid
        } else {
            left = mid + 1
        }
    }
    
    
    return right
};

但现在的问题是要求将所有索引返回到本地最大数字。除了遍历数组并逐个检查数字以查看它是否大于其前一个数字和下一个数字之外,我想不出任何其他方法。

【问题讨论】:

  • 你想到的方法有什么问题?

标签: javascript algorithm binary-search


【解决方案1】:

您必须扫描整个阵列,这需要O (n) 时间。一个简单的证明是,当您尝试 [2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, ..., 2, 1, 2] 时,一半的值将是局部最大值,因此任何检查这一点的算法都将至少采用 n / 2 操作(某种形式)。

这是一个相当简单的版本,它报告所有峰值,对第一个和最后一个索引进行单独处理,对其余索引进行通用处理。它假设您只想要真正的峰值而不是高原。如果您想要高原指数,则必须将一些 &lt; / &gt; 标志替换为 &lt;= / &gt;= 标志。

const localMaxima = (ns) => [
  ... (ns [0] > ns [1] ? [0] : []),
  ... Array .from ({length: ns.length - 1}, ((_, i) => i + 1)) 
        .filter ((i) => ns [i - 1] < ns [i] && ns [i + 1] < ns [i]),
  ... (ns [ns .length - 1] > ns [ns .length - 2] ? [ns .length - 1] : [])
]

console .log (localMaxima ([1, 2, 1, 3, 5, 6, 4])) //=> [1, 5]
//                             ^           ^
console .log (localMaxima ([8, 5, 7, 5, 3, 0, 9])) //=> [0, 2, 6]
//                          ^     ^           ^
console .log (localMaxima ([2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2])) //=> [0, 2, 4, 6, 8, 10]
//                          ^     ^     ^     ^     ^     ^
.as-console-wrapper {max-height: 100% !important; top: 0}

请注意,第一个示例返回 [1, 5]。问题中的[2, 5] 只是一个错字吗?

更新

要求使此“更具可读性”的评论。

我绝对应该做一件事。我在这里内联了一个range 函数。没有理由这样做。使用单独的函数时,它更具可读性。 (range 是一个返回整数范围的简单函数。例如range (3, 12) //=&gt; [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]。)

使用它会导致对我来说是一个可读性很强的版本:

const range = (lo, hi) => Array .from ({length: hi - lo + 1}, (_, i) => lo + i)

const localMaxima = (ns) => [
  ... (ns [0] > ns [1] ? [0] : []),
  ... range (1, ns.length - 2) .filter ((i) => ns [i - 1] < ns [i] && ns [i + 1] < ns [i]),
  ... (ns [ns .length - 1] > ns [ns .length - 2] ? [ns .length - 1] : [])
]

但是,我不认为这是被问到的。我认为请求的代码可能看起来像这样:

const localMaxima = (ns) => {
  const includeStart = ns [0] > ns [1]
  const includeEnd = ns [ns.length - 1] > ns [ns .length -2]
  const intermediates = range (0, ns .length - 2)
    .filter ((i) => ns [i - 1] < ns [i] && ns [i + 1] < ns [i])
  if (includeStart) intermediates .unshift (0)
  if (includeEnd) intermediates .push (ns .length - 1)
  return intermediates
}

但我一点也不喜欢。我尽我所能处理不可变数据,unshiftpush 调用对我来说太可怕了。

可能我最接近那个并且仍然照镜子的方式是

const localMaxima = (ns) => {
  const includeStart = ns [0] > ns [1]
  const includeEnd = ns [ns.length - 1] > ns [ns .length -2]
  const intermediates = range (0, ns .length - 2)
    .filter ((i) => ns [i - 1] < ns [i] && ns [i + 1] < ns [i])
  const beginning = includeStart ? [0] : []
  const ending = includeEnd ? [ns .length - 1] : []
  return beginning .concat (intermediates) .concat (ending)
}

我也不太喜欢这些一次性变量赋值,但在像 JS 这样的语言中,它们有时很难避免。至少在这里他们仍然是const。我会避免替换这个:

  const beginning = includeStart ? [0] : []

使用这种替代方法

  let beginning
  if (includeStart) {
    beginning = [0]
  } else {
    beginning = []
  }

因此,如果您只是不喜欢条件运算符(三元组),我们可能永远不会看到一致的意见!

我非常愿意在任何一天选择我的帖子-range 重构这些版本。但可读性在旁观者眼中,也许最后一个会让一些人高兴。

【讨论】:

  • 您好,感谢您的回答。你能让你的解决方案更具可读性吗?我很难遵循所有这些条件检查
  • @Joji:添加了更新。经过重要的重构后,我不会以任何我个人使用的方式对其进行更改,但也许它更符合您的喜好。
【解决方案2】:

据我所知,您的方法是正确的。您确实需要查看三个值(当前、上一个和下一个)以确定一个数字是否是局部最大值。

暂时将其从 JavaScript 中抽象出来,并仅从数学上考虑它,找到连续函数的局部最大值(这是该整数数组的近似模型)意味着找到其一阶导数所在的点(即它的梯度)为0,它的二阶导数(即初始函数梯度的梯度)为负。也就是说,函数是平坦的,向下弯曲的点。

要在数值上找到函数的导数,您需要查看函数上的两个点。寻找二阶导数也是如此——您需要查看导数函数上的两个点。导数函数中的这两个点都是根据原始函数中的两个点计算得出的,但其中一个点是共享的,因此基本上您需要查看原始函数中的三个点才能找到二阶导数。

请记住,您并不总是需要进行两次比较。如果一个数字小于之前的数字,当然你不需要检查下一个数字,因为你已经知道你没有找到局部最大值。但是,您可能需要考虑连续多次出现相同数字的边缘情况,以及是否每个都应计为局部最大值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-14
    • 2016-05-18
    相关资源
    最近更新 更多