在排序数组中查找总和为X的整数对的功能方法

时间:2017-02-17 20:34:28

标签: arrays algorithm scala functional-programming

这是我之前question的后续内容。 假设我想在给定的排序数组中找到一对整数,它们总和给定数字x。众所周知的“一次通过”解决方案看起来像这样:

def pair(a: Array[Int], target: Int): Option[(Int, Int)] = {

  var l = 0
  var r = a.length - 1
  var result: Option[(Int, Int)] = None
  while (l < r && result.isEmpty) {
    (a(l), a(r)) match {
      case (x, y) if x + y == target => result = Some(x, y)
      case (x, y) if x + y < target => l = l + 1
      case (x, y) if x + y > target => r = r - 1
    }
  }
  result
}

你如何建议在没有任何可变状态的情况下进行功能性写作? 我想我可以用Stream编写一个递归版本(Scala中的惰性列表) 你能推荐一个非递归版本吗?

4 个答案:

答案 0 :(得分:4)

这是一个相当简单的版本。它创建Stream Vectors,删除每次迭代的第一个或最后一个元素。然后我们限制其他无限Stream的大小(-1这样你就不能自己添加一个数字),然后map将它输入输出格式并检查目标条件。 / p>

def findSum(a: Vector[Int], target: Int): Option[(Int, Int)] = {
  def stream = Stream.iterate(a){
    xs => if (xs.head + xs.last > target) xs.init else xs.tail
  }

  stream.take (a.size - 1)
        .map {xs => (xs.head, xs.last)}
        .find {case (x,y) => x + y == target}
}

Scala集合的伴随对象中隐藏了很多宝石,例如Stream.iterate。我强烈建议您查看它们。了解它们可以大大简化这样的问题。

答案 1 :(得分:2)

这是一个没有使用索引的版本(除非有重要的计算值,否则我会尽量避免使用索引):

label.text = [rawText stringByReplacingOccurrencesOfString@"\\n" withString:@"\n"];

它是递归的,但不需要Stream。 def findPair2(x: Int, a: Array[Int]): Option[(Int, Int)] = { def findPairLoop(x: Int, l: Array[Int], r: Array[Int]): Option[(Int, Int)] = { if (l.head >= r.last) None else if (l.head + r.last == x) Some((l.head, r.last)) else if (l.head + r.last > x) findPairLoop(x, l, r.init) else findPairLoop(x, l.tail, r) } findPairLoop(x, a, a) } tail对于数组是O(N)但如果我们使用列表并反转init集合以避免rinit O(N)版本可以完成

last

如果我们不关心一次通过(或效率一般:))它是一个单线

  def findPairInOrderN(x: Int, a: Array[Int]): Option[(Int, Int)] = {
    def findPairLoop(x: Int, l: List[Int], r: List[Int]): Option[(Int, Int)] = {
        if (l.head >= r.head) None
        else if (l.head + r.head == x) Some((l.head, r.head))
        else if (l.head + r.head > x) findPairLoop(x, l, r.tail)
        else findPairLoop(x, l.tail, r)
    }
    val l = a.toList
    findPairLoop(x, l, l.reverse)
}  

将它展开到flatmap / map然后使用collectFirst给我们这个,这是相当整洁和更优化的(但仍然不是O(n)) - 它停在第一个正确的对,但做了更多的工作而不是到达那里

(for (m <-a ; n <- a if m + n == x) yield (m,n)).headOption

答案 2 :(得分:1)

如果没有递归并且没有可变状态,它可能会非常难看。这是我的尝试:

def f(l: List[Int], x: Int): Option[(Int, Int)] = {
  l.foldLeft(l.reverse) {
    (list, first) =>
      list.headOption.map {
        last =>
          first + last match {
            case `x` => return Some(first, last)
            case sum if sum < x => list
            case sum if sum > x =>
              val innerList = list.dropWhile(_ + first > x)
              innerList.headOption.collect {
                case r if r + first == x => return Some(first, r)
              }.getOrElse {
                innerList
              }
          }
      }.getOrElse {
        return None
      }
  }
  None
}

示例:

scala> f(List(1, 2, 3, 4, 5), 3)
res33: Option[(Int, Int)] = Some((1,2))

scala> f(List(1, 2, 3, 4, 5), 9)
res34: Option[(Int, Int)] = Some((4,5))

scala> f(List(1, 2, 3, 4, 5), 12)
res36: Option[(Int, Int)] = None

开头的.reverse加上foldLeft并在找到结果后返回O(2n)

答案 3 :(得分:1)

这是一个单线

[ (x, y) | x <- array, y <- array, x+y == n ]

它甚至适用于未排序的列表。

但是如果你想利用排序,只需对数组中的每个(n-x)进行x的二进制搜索,而不是通过数组。