【问题标题】:JavaScript: return all contiguous subarrays whose sum equals KJavaScript:返回总和等于 K 的所有连续子数组
【发布时间】:2021-03-16 05:27:52
【问题描述】:

这是leetcode question 的一个变体,但我们想要返回实际的连续 子数组,而不是返回计数。例如,如果 num = [1,2,4,7] k=7 返回值应该是 [[1,2,4],[7]]

我使用哈希图来存储所有可能的索引的累积总和以及原始问题出现相同总和的次数,它要求返回count

var subarraySum = function(nums, k) {
  let sum = 0;
  let count = 0;
  const myMap = new Map();
  myMap.set(0, 1);

  for (let num of nums) {
    sum += num;
    count += myMap.get(sum - k) || 0;
    myMap.set(sum, (myMap.get(sum) || 0) + 1);
  }
  
  return count;
}

但我似乎无法弄清楚如何调整此解决方案以返回实际的子数组。

【问题讨论】:

    标签: javascript algorithm


    【解决方案1】:

    解决方案

    要返回所有匹配的连续子数组,你应该使用你的myMap 有点不同。对于每个键sum,它应该存储索引,而不是sum的频率。

    此外,您的ans 应该包含一个匹配的连续子数组的数组,并且它会根据存储在myMap 中的索引进行更新。

    基本上,如果有sum - k 的索引,那么它们就是新匹配子数组的起始索引,而当前索引i 是结束索引。因此,您遍历所有起始索引并添加新的匹配子数组,如下所示:

    ...
        if (myMap.has(key)) {
          indices = myMap.get(key) 
          for (const index of indices) {
            matchingSubArrayIndices.push([index, i])    
          }
        } 
    ... 
    

    请在下面找到完整的代码:

    var subarrays = function(nums, k) {
      let sum = 0;
      const myMap = new Map();
      
      myMap.set(0, [0]);
      const matchingSubArrayIndices = new Array()
    
      for (var i = 0; i < nums.length; i++) {
        num = nums[i]
        sum += num;
        let key = sum - k
        if (myMap.has(key)) {
          indices = myMap.get(key) 
          for (const index of indices) {
            matchingSubArrayIndices.push([index, i])    
          }
        }
        
        array = myMap.get(sum)
        if (array == undefined) {
          array = new Array() 
        }
        array.push(i + 1)
        myMap.set(sum, array);
      }
      
      for (let indices of matchingSubArrayIndices) {
        console.log("Matching subarray")
        for (i = indices[0]; i <= indices[1]; i++) {
            console.log(nums[i] + " ")
        }
      }
    }
    
    subarrays([1,3,4], 4)

    此解决方案对大多数输入都非常有效,但对于如下输入:

    0 0 0 0
    0
    

    它在时间和空间上都是二次的,因为在每次迭代中,matchingSubArrayIndices 将线性增长,因此对于所有迭代,它都是二次的。

    结论

    即使算法在最坏的情况下是二次的,但这不是算法本身的问题,而是输出的问题。如果输出是二次的,那么任何解都至少是二次的。

    【讨论】:

      【解决方案2】:

      这是一个幼稚的实现,但它非常简单。我们只需找到所有连续(非空)子数组,然后过滤以找到总和与我们的目标匹配的那些:

      const sum = (ns) => ns.reduce((a, b) => a + b, 0)
      const suffixes = (xs) => xs .map ((_, i) => xs .slice (i))
      const prefixes = (xs) => xs .map ((_, i) => xs .slice (0, i + 1))
      const subArrays = (xs) => suffixes (xs) .flatMap (prefixes)
      
      const subArraysWithTotal = (ns, t) => subArrays (ns) .filter ((s) => sum (s) == t)
      
      console .log (
        subArraysWithTotal ([1, 2, 4, 7], 7)
      )

      sum 很明显,只需将数组中的数字相加即可。

      prefixessuffixes 相似,举个例子应该清楚:

      prefixes (['w', 'x', 'y', 'z'])
      //=> [['w'], ['w', 'x'], ['w', 'x', 'y'], ['w', 'x', 'y', 'z']]
      
      suffixes (['w', 'x', 'y', 'z'])
      //=> [['w', 'x', 'y', 'z'], ['x', 'y', 'z'], ['y', 'z'], ['z']]
      

      subArrays 结合这些,返回每个后缀的所有前缀,这为我们提供了原始的所有连续子数组:

      subArrays (['w', 'x', 'y', 'z'])
      //=> [['w'], ['w', 'x'], ['w', 'x', 'y'], ['w', 'x', 'y', 'z'], 
      //    ['x'], ['x', 'y'], ['x', 'y', 'z'], ['y'], ['y', 'z'], ['z']]
      

      我们的主要函数 subArraysWithTotal 只是找到所有这些子数组,然后通过计算它们的总和来过滤它们,并将其与我们的目标进行比较。

      正如我所说,这很幼稚。它将是空间上的O(n^2) 和时间上的O(n^3)。如果我们知道我们所有的数字都是正数,那么某种滑动窗口技术肯定会提高速度和内存。由于负数的可能性,仍然可能有一个更便宜的算法,但它并不那么明显。

      但是我们可以使用生成器函数来减少内存。将上述所有内容(sum 除外)替换为生成器并为生成器添加 filter 函数的替代版本可能如下所示:

      const sum = (ns) => ns.reduce((a, b) => a + b, 0)
      function* suffixes (xs) {for (let i of xs .keys ()) yield xs .slice (i)}
      function* prefixes (xs) {for (let i of xs .keys ()) yield xs .slice (0, i + 1)}
      function* subs (xs) {for (let suff of suffixes (xs)) yield* prefixes (suff)}
      function* filter (fn, it) {for (let x of it) {if (fn (x)) yield x}}
      
      function* subArraysWithTotal (ns, t) {yield * filter ((s) => sum (s) == t, subs (ns))}
      
      console .log (
        [...subArraysWithTotal ([1, 2, 4, 7], 7)]
      )

      现在我们只需要足够的内存来保存当前子数组。

      【讨论】:

        【解决方案3】:

        以下是一个有效的解决方案,对您所指的代码进行了细微调整。此解决方案迭代输入数组一次 + 将子数组添加到解决方案所需的任何内容。

        在下面的行中,您知道计数是否增加,您有一些东西要添加到您的解决方案中。

        count += myMap.get(sum - k) || 0;
        

        现在我们需要了解sum - k 存在于地图中的哪个索引处。在下面一行中,您只计算总和出现的次数。

        myMap.set(sum, (myMap.get(sum) || 0) + 1);
        

        您需要存储发生总和的索引*,而不仅仅是计数。摆脱整个代码中的计数,只关注索引。

        伪代码现在如下所示:

        for(int i = 0; i < nums.size(); i++):
            sum += nums[i]
            if(sum-k exists in the map):
                start_index = get the index corresponding to that sum-k
                //end_index will be the i
                add to the result set the subarray from start_index to i (or end_index)
            Set in map the sum and index i appropriately
        

        *我希望得到的子数组不要重叠。否则,存储索引列表而不是索引。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-06-25
          • 2015-11-05
          • 2012-11-08
          • 2016-12-20
          • 1970-01-01
          • 2016-12-29
          • 1970-01-01
          • 2014-05-18
          相关资源
          最近更新 更多