我正在尝试用给定的总和实现查找子阵列的功能样式。 我写的代码不符合功能风格。有人可以帮助使其更具功能性。
问题:给定一个未排序的非负整数数组,找到一个连续的子数组,它会添加到给定的数字。
输入: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")
}
答案 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只会通过列表一次。这一个依赖于非负的整数(因此添加另一个元素不能减少总数),其他元素也使用负整数。