【问题标题】:Tail recursive solution in Scala for Linked-List chainingScala中用于Linked-List链接的尾递归解决方案
【发布时间】:2020-06-26 15:51:03
【问题描述】:

我想在 Leetcode 上为以下问题写一个尾递归解决方案 -

给定两个代表两个非负整数的非空链表。数字以相反的顺序存储,它们的每个节点都包含一个数字。将两个数字相加并作为链表返回。

你可以假设这两个数字不包含任何前导零,除了数字 0 本身。

例子:

*Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)*
*Output: 7 -> 0 -> 8*
*Explanation: 342 + 465 = 807.*

Link to the problem on Leetcode

我无法找到在最后一行调用递归函数的方法。 我在这里试图实现的是递归调用 add 函数,该函数将两个列表的头部添加一个进位并返回一个节点。返回的节点与调用堆栈中的节点链接在一起。

我对 scala 很陌生,我猜我可能错过了一些有用的构造。

/**
 * Definition for singly-linked list.
 * class ListNode(_x: Int = 0, _next: ListNode = null) {
 *   var next: ListNode = _next
 *   var x: Int = _x
 * }
 */
import scala.annotation.tailrec
object Solution {
  def addTwoNumbers(l1: ListNode, l2: ListNode): ListNode = {
    add(l1, l2, 0)
  }
  //@tailrec
  def add(l1: ListNode, l2: ListNode, carry: Int): ListNode = {
    var sum = 0;
    sum = (if(l1!=null) l1.x else 0) + (if(l2!=null) l2.x else 0) + carry;
    if(l1 != null || l2 != null || sum > 0)
      ListNode(sum%10,add(if(l1!=null) l1.next else null, if(l2!=null) l2.next else null,sum/10))
    else null;
  }
}

【问题讨论】:

  • 顺便说一句,似乎 LeetCode 并不适合 Scala,它们使用标准库 中的数据结构(像这样NodeList) 并且他们的大部分问题都有望以命令式的方式解决(这不是 Scala 中的规范)。如果您的最终目标是学习 Scala,则最好使用 Scala Exercises99 Scala Problems

标签: algorithm scala linked-list tail-recursion


【解决方案1】:

你有几个问题,大部分可以归结为不习惯。

varnullScala 中并不常见,通常,您会使用尾递归算法来避免这种情况。

最后,请记住尾递归算法要求最后一个表达式是纯值或递归调用。为此,您通常会跟踪剩余的工作以及累加器。

这是一个可能的解决方案:

type Digit = Int // Refined [0..9]
type Number = List[Digit] // Refined NonEmpty.

def sum(n1: Number, n2: Number): Number = {
  def aux(d1: Digit, d2: Digit, carry: Digit): (Digit, Digit) = {
    val tmp = d1 + d2 + carry
    val d = tmp % 10
    val c = tmp / 10
    
    d -> c
  }

  @annotation.tailrec
  def loop(r1: Number, r2: Number, acc: Number, carry: Digit): Number =
    (r1, r2) match {
      case (d1 :: tail1, d2 :: tail2) =>
        val (d, c) = aux(d1, d2, carry)
        loop(r1 = tail1, r2 = tail2, d :: acc, carry = c)

      case (Nil, d2 :: tail2) =>
        val (d, c) = aux(d1 = 0, d2, carry)
        loop(r1 = Nil, r2 = tail2, d :: acc, carry = c)

      case (d1 :: tail1, Nil) =>
        val (d, c) = aux(d1, d2 = 0, carry)
        loop(r1 = tail1, r2 = Nil, d :: acc, carry = c)

      case (Nil, Nil) =>
        acc
    }

  loop(r1 = n1, r2 = n2, acc = List.empty, carry = 0).reverse
}

现在,这种递归往往非常冗长。
通常,stdlib 提供了使相同算法更简洁的方法:

// This is a solution that do not require the numbers to be already reversed and the output is also in the correct order.
def sum(n1: Number, n2: Number): Number = {
  val (result, carry) = n1.reverseIterator.zipAll(n2.reverseIterator, 0, 0).foldLeft(List.empty[Digit] -> 0) {
    case ((acc, carry), (d1, d2)) =>
      val tmp = d1 + d2 + carry
      val d = tmp % 10
      val c = tmp / 10
      (d :: acc) -> c
  }


  if (carry > 0) carry :: result else result
}

【讨论】:

    【解决方案2】:

    Scala 在 LeetCode 上不太受欢迎,但这个解决方案(不是最好的)会被 LeetCode 的在线评委接受:

    import scala.collection.mutable._
    object Solution {
        def addTwoNumbers(listA: ListNode, listB: ListNode): ListNode = {
            var tempBufferA: ListBuffer[Int] = ListBuffer.empty
            var tempBufferB: ListBuffer[Int] = ListBuffer.empty
            tempBufferA.clear()
            tempBufferB.clear()
    
            def listTraversalA(listA: ListNode): ListBuffer[Int] = {
                if (listA == null) {
                    return tempBufferA
    
                } else {
                    tempBufferA += listA.x
                    listTraversalA(listA.next)
                }
            }
    
            def listTraversalB(listB: ListNode): ListBuffer[Int] = {
                if (listB == null) {
                    return tempBufferB
    
                } else {
                    tempBufferB += listB.x
                    listTraversalB(listB.next)
                }
            }
            val resultA: ListBuffer[Int] = listTraversalA(listA)
            val resultB: ListBuffer[Int] = listTraversalB(listB)
            val resultSum: BigInt = BigInt(resultA.reverse.mkString) + BigInt(resultB.reverse.mkString)
            var listNodeResult: ListBuffer[ListNode] = ListBuffer.empty
            val resultList = resultSum.toString.toList
            var lastListNode: ListNode = null
    
            for (i <-0 until resultList.size) {
                if (i == 0) {
                    lastListNode = new ListNode(resultList(i).toString.toInt)
                    listNodeResult += lastListNode
    
                } else {
                    lastListNode = new ListNode(resultList(i).toString.toInt, lastListNode)
                    listNodeResult += lastListNode
                }
            }
    
            return listNodeResult.reverse(0)
        }
    }
    

    参考

    • 有关其他详细信息,您可以查看Discussion Board。那里有大量公认的解决方案、解释、多种语言的高效算法,以及时间/空间复杂度分析。

    【讨论】:

    • 如果我正在采访某人并且他们给了我这个解决方案,他们会立即被丢弃。
    • 是的,我的不会通过,因为我使用的是 Scala 内置数据结构而不是 ListNode。我想澄清一下,这本身并不是一个糟糕的解决方案,它对 Scala 来说是一个糟糕的解决方案。实际上,正如我在另一条评论中解释的那样,对于我在几个问题中看到的情况,LeetcodeScala 并不同情。
    猜你喜欢
    • 2020-07-13
    • 2023-04-03
    • 2021-12-01
    • 2013-07-22
    • 1970-01-01
    • 2016-05-21
    • 1970-01-01
    • 2015-10-25
    • 1970-01-01
    相关资源
    最近更新 更多