【问题标题】:The improved solution for 2 sum algorithm2 sum算法的改进方案
【发布时间】:2017-08-12 12:57:48
【问题描述】:

我尝试在 leetcode (https://leetcode.com/problems/two-sum/#/description) 中制定算法的功能实现

我想我需要的是以下 3 个函数:

  • List[Int] => List[(Int, Int)] 用位置压缩元素
  • List[(Int, Int)], Int, Int => List[((Int, Int) , (Int, Int))] 压缩元素和位置,其余元素和位置和过滤器使用 Sum
  • List[((Int, Int) , (Int, Int))] => List[(Int, Int)]映射到该位置

代码如下所示:

  def findTwoSumElements(xs: List[Int], sum: Int): List[(Int, Int)] = {
    val zipWithIndex = xs.zipWithIndex
    var tailElements = zipWithIndex

    val result = zipWithIndex.map(t => {
      val element = zipAndFilter(tailElements, t, sum)
      tailElements = tailElements.tail
      element
     }
    )

    result.flatten.map(t => (t._1._2, t._2._2))
  }

  private def zipAndFilter(xs: List[(Int, Int)], x: (Int, Int), sum: Int): List[((Int, Int), (Int, Int))] = {
    xs.map(t => (t, x)).filter(t => (t._1._1 + t._2._1) == sum)
  }

 println(findTwoSumElements(List(1,2,3,4), 10)) //List()
 println(findTwoSumElements(List(1,2,3,4), 7)) //List((3,2))
 println(findTwoSumElements(List(1,2,3,4), 5)) //List((3,0), (2,1))
 println(findTwoSumElements(List(), 5)) //List()

我想通过不使用var来改进这部分代码

  var tailElements = zipWithIndex

  val result = zipWithIndex.map(t => {
     val element = zipAndFilter(tailElements, t, sum)
     tailElements = tailElements.tail
     element
   }
  )

原因是我想去掉重复的Tuple[Int, Int],比如不变异尾巴就会一起返回(x, y)和(y, x)

我可以提供一些关于如何改进它以及整个实现的建议或演示代码

【问题讨论】:

    标签: scala functional-programming


    【解决方案1】:

    对于每个输入只有一个解决方案的简化问题,您可以将解决方案编写为单行:

    def twoSum(nums: List[Int], target: Int): List[Int] =
      nums.combinations(2).find(_.sum == target).get.map(nums.indexOf)
    

    扩展解决方案,具有显式类型:

    def twoSum(nums: List[Int], target: Int): List[Int] = {
      val comb: Iterator[List[Int]] = nums.combinations(2) // Get 2-sized combinations iterator
      val find: Option[List[Int]] = comb.find(_.sum == target) // Find the first (and only) combination having sum equals to our target
      val res: List[Int] = find.get // Exactly one solution
      val idx: List[Int] = res.map(nums.indexOf) // Get the indexes in the original list
    }
    

    一种替代的通用解决方案,允许多个结果或根本没有结果(返回 List[List[Int]]):

     def twoSum(nums: List[Int], target: Int): List[List[Int]] =
       nums.combinations(2).collect {
         case couple if couple.sum == target =>
           couple.map(nums.indexOf)
       }.toList
    

    它可以更广泛地接受组合大小(在我们的示例中为2)作为参数

    【讨论】:

    • 很高兴知道组合!非常感谢。最好使用过滤器而不是查找,它可能有多个匹配的元素。另外,我们可以跳过get的使用,它可能会返回none值。
    • 作业明确指出“正是一种解决方案”。我将使用通用解决方案更新答案。
    • 上面的单行解决方案给出了不正确的输出,对于 twoSum(List(3,3), 6) 它应该根据 leet 代码问题导致 List(0,1) 但它给出 List( 0,0)
    【解决方案2】:

    好的,我会拍个照 :)

    下面的解决方案怎么样:

      def twoSum(nums: Seq[Int], target: Int): Seq[Int] =
        nums.zipWithIndex.
          filter(x =>
            (nums.take(nums.indexOf(x._1)) ++ nums.drop(nums.indexOf(x._1)+1))
            .contains(target - x._1)).map(_._2)
    

    这是正在做的事情(以数组 [2, 7, 11, 15] 和 target=9 为例):

    1. 我们zip with index 获取每个数字与其位置配对,例如(2,0),(7,1)...
    2. 我们过滤每一对,根据其与原始集合中目标(即目标 - 数字)的数量差异(参见 nums.take++ nums.drop
    3. 我们将每个结果对映射到其位置部分(例如 (2,0) => 0, (7,1) => 1)
    4. 生成的序列仅包含数字的位置,这些数字具有添加到目标的相应和对:(0,1)。

    【讨论】:

      【解决方案3】:

      combinations(2) 总是会给你 O(n^2) 复杂度。 Leetcode 将使您的解决方案因大量输入而超时。

      最后一个.map(nums.indexOf) 将不起作用,因为您将得到“[3, 3] 寻找 6”的错误答案。

      我建议的一个解决方案是

      def twoSum(nums: Array[Int], target: Int): Array[Int] = {
          import scala.collection.immutable.HashMap 
          def run(index: Int, map: HashMap[Int, Int]): Array[Int] = {
              val value = nums(index)
              map get (target - value) match {
                  case Some(foundInd) => Array(foundInd, index)
                  case None => run(index + 1, map + (value -> index))
              }
          }
          run(0, HashMap())
      }
      

      它不是很花哨,但它以线性时间运行,适用于所有测试用例,并且没有突变。

      【讨论】:

        【解决方案4】:

        Scala 有一个内置的combinations 方法可以为您完成很多工作。这使得查找目标总和变得如此简单:

        val result = nums.combinations(2).filter{case List(x, y) => x + y == target}.next
        

        然后您可以通过以下方式将答案映射回索引:

        val indices = result map (nums.zipWithIndex.toMap)
        

        【讨论】:

        • 不错的解决方案,但如果你得到 [3,3] 呢?这将返回 [1,1] 而不是 [0,1]
        【解决方案5】:
         def sol(inputArray: Array[Int], target: Int): Array[Int] = {
        
            def recPass(list: List[(Int, Int)], map: Map[Int, Int]): Array[Int] = list match {
              case (elem, index) :: tail =>
                val complement = target - elem
                if (map.contains(complement)) Array(map(complement), index)
                else recPass(tail, map + (elem -> index))
              case _ => throw new IllegalArgumentException("No two sum solution")
            }
            recPass(inputArray.zipWithIndex.toList, Map.empty)
          }
        

          def sol(nums: Array[Int], target: Int): Array[Int] = {
              def tailRec(map: Map[Int, Int], index: Int): Array[Int] = {
        
          if (index >= nums.length) throw new IllegalArgumentException("No two sum solution") else {
            if (map.contains(nums(index))) {
              Array(map(nums(index)), index)
            } else {
              tailRec(map + (target - nums(index) -> index), index + 1)
            }
          }
        }
          tailRec(Map(), 0)
          }
        

        【讨论】:

        • 请详细说明您发布这些答案的原因。与 4 年前发布的其他答案相比,它们的优点/缺点是什么。
        • 好吧,首先,上面的大多数答案并不适用于所有情况,正如我在上面的评论中提到的那样,其次我的解决方案不使用 vars,效率为 89%,内存更少然后是 leet 代码上的所有其他 scala 解决方案。
        • 值得包含在已发布答案中而不是隐藏在 cmets 中的信息。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-12-30
        • 1970-01-01
        • 2012-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多