找到给定总和的子阵列

时间:2015-04-05 06:48:03

标签: algorithm scala functional-programming

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

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

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

输入:arr [] = {1,4,0​​,0,3,10,5},sum = 7 Ouptut:在索引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大于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")
  }

1 个答案:

答案 0 :(得分:1)

val list:List[Int] = List(1, 4, 20, 3, 10, 5)
val sum = 33

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

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的一般用处)

编辑:这是您发布的更具“功能性”的代码版本。但我认为我的第一个版本更清晰 - 将产生序列的问题与检查它们的总和分开是更简单的

 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)。 “功能性”,因为没有可变变量,但在我看来,它不如其他变量清晰。如果您只是在找到结果时打印结果(不需要在迭代之间携带累加器的rs部分),这会更清楚一点,但这种副作用方式看起来功能较少,所以我返回一个解决方案列表。

 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只会通过列表一次。这一个依赖于非负的整数(因此添加另一个元素不能减少总数),其他元素也使用负整数。