查找具有给定总和的子数组

Find subarray with given sum

我正在尝试实现查找具有给定总和的子数组的功能样式。 我写的代码不符合功能风格。有人可以帮助使其更具功能性吗?

问题:给定一个未排序的非负整数数组,找到一个与给定数字相加的连续子数组。

输入:arr[] = {1, 4, 20, 3, 10, 5},总和 = 33 输出:在索引 2 和 4 之间找到的总和

输入:arr[] = {1, 4, 0, 0, 3, 10, 5},总和 = 7 输出:在索引 1 和 4 之间找到的总和

我可以用蛮力法解决这个问题。但正在寻找更有效的功能解决方案。

val sumList = list.foldLeft(List(0), 0)((l, r) => (l._1 :+ (l._2+r), l._2 + r))._1.drop(1)
  //Brute force approach
  sumList.zipWithIndex.combinations(2).toList.collectFirst({
    case i if i(1)._1 - i(0)._1 == sum => i
  }) match {
    case Some(List(x, y)) => println("elements which form the given sum are => "+ list.drop(x._2+1).take(y._2-x._2))
    case _ => println("couldn't find elements which satisfy the given condition")
  }

算法: 初始化一个变量curr_sum作为第一个元素。 curr_sum表示当前子数组的和。从第二个元素开始,将所有元素一一添加到curr_sum中。如果 curr_sum 等于 sum,则打印解决方案。如果 curr_sum 超过总和,则在 curr_sum 大于总和时删除尾随元素。

 val list:List[Int] = List(1, 4, 20, 3, 10, 5)
  val sum = 33
    
  val (totalSum, start, end, isSumFound) = list.zipWithIndex.drop(1).foldLeft(list.head, 0, 1, false)((l, r) =>
    if(!l._4) {
      val tempSum = l._1 + r._1
      if (tempSum == sum){
        (sum, l._2, r._2, true)
      } else if(tempSum > sum){
          var (curSum, curIndex) = (tempSum, l._2)
          while(curSum > sum && curIndex < list.length-1){
            curSum = curSum - list(curIndex)
            curIndex = l._2 +1
          }
        (curSum, curIndex, r._2, curSum == sum)
      } else {
        (tempSum, l._2, r._2, false)
      }
    }else
      l
  )

  if(isSumFound || totalSum == sum){
    println("elements which form the given sum are => "+ list.drop(start+1).take(end-start))
  }else{
    println("couldn't find elements which satisfy the given condition")
  }
val list:List[Int] = List(1, 4, 20, 3, 10, 5)
val sum = 33

return 子列表迭代器的方法,首先是从第一个元素开始的子列表,然后从第二个元素开始...

def subLists[T](xs:List[T]):Iterator[List[T]] =
     if (xs == Nil) Iterator.empty
     else xs.inits ++ subLists(xs.tail)

找到第一个总和正确的列表

val ol = subLists(list).collectFirst{ case x if x.sum == sum => x}

然后再次查找索引,并打印结果

ol match {
    case None => println("No such subsequence")
    case Some(l) => val i = list.indexOfSlice(l)
                println("Sequence of sum " + sum +
                        " found between " + i +
                         " and " + (i + l.length - 1))
} 
//> Sequence of sum 33 found between 2 and 4

(您可以在构建迭代器时跟踪与子列表关联的索引,但这似乎比它的价值更麻烦,并且降低了 subLists 的一般用途)

编辑:这是您发布的代码的一个版本 "functional"。但我认为我的第一个版本更清晰——将生成序列的关注点与检查它们的总和分开更简单

 val sumList = list.scanLeft(0){_ + _}
 val is = for {i <- 1 to list.length - 1
                j <- 0 to i
                if sumList(i)-sumList(j) == sum}
             yield (j, i-1)

 is match {
    case Seq() => println("No such subsequence")
    case (start, end) +: _ =>
                 println("Sequence of sum " + sum +
                         " found between " + start + " and " + end )
 } 
 //> Sequence of sum 33 found between 2 and 4

EDIT2:这是一个 O(N) 的问题。 "Functional" 因为没有可变变量,但在我看来,它不如其他变量清楚。如果你只打印找到的结果会更清楚一点(不需要在迭代之间携带累加器的 rs 部分)但是这种副作用的方式似乎不太实用,所以我 return a解决方案列表。

 val sums = list.scanLeft(0)(_ + _) zipWithIndex
 sums.drop(1).foldLeft((sums, List[(Int, Int)]())) {
    case ((leftTotal, rs), total) =>
      val newL = leftTotal.dropWhile(total._1 - _._1 > target)
      if (total._1 - newL.head._1 == target)
        (newL, (newL.head._2, total._2 - 1) :: rs)
      else (newL, rs)
  }._2
  //> res0: List[(Int, Int)] = List((2,4))

O(N) 因为我们将缩短的 newL 作为下一次迭代传递给 leftTotal,所以 dropWhile 只会遍历列表一次。这个依赖于非负整数(因此添加另一个元素不能减少总数),其他的也使用负整数。