【问题标题】:Plus One Linked List: Functional Approach加一链表:函数式方法
【发布时间】:2021-03-29 11:49:44
【问题描述】:

一直在努力解决这个 leetcode 问题。想知道是否有一种实用的方法来解决这个问题。我能想到的就是使用可变对象(在 Scala 中)。

给定一个表示为数字链表的非负整数,该整数加一。

存储数字时,最高有效数字位于列表的开头。

示例 1:

输入:head = [1,2,3] 输出:[1,2,4]

示例 2:

输入:head = [0] 输出:[1]

约束:

链表的节点数在[1, 100]范围内。

0 <= Node.val <= 9

链表表示的数字除了零本身外不包含前导零。

 * Definition for singly-linked list.
 * class ListNode(_x: Int = 0, _next: ListNode = null) {
 *   var next: ListNode = _next
 *   var x: Int = _x
 * }
 */
object Solution {
    def plusOne(head: ListNode): ListNode = {
        
    }
}

更有趣的输入/输出 = [1,9,9,9,9] =&gt; [2,0,0,0,0]

【问题讨论】:

  • 顺便说一句,ListNode 的定义不是很像 Scala,它至少应该是 class ListNode(val x: Int = 0, val next: ListNode = null),尽管 null 应该替换为一些代表空列表节点的对象。跨度>
  • 我不反对。就像 leetcode 定义的那样。
  • 您可能想尝试一些专门为 Scala 量身定制的在线资源。这篇文章有一个list(不过我还没有尝试过那里的任何课程,所以我无法证明它们的用处)。

标签: scala functional-programming


【解决方案1】:

(tail) 递归救援!

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

def plusOne(number: Number): Number = {
  def sumDigits(d1: Digit, d2: Digit): (Digit, Digit) = {
    val r = d1 + d2
    (r % 10) -> (r / 10)
  }
  
  @annotation.tailrec
  def loop(remaining: Number, remainder: Digit, acc: Number): Number =
    remaining match {
      case digit :: digits =>
        val (result, newRemainder) = sumDigits(digit, remainder)
        loop(remaining = digits, newRemainder, result :: acc)
      
      case Nil =>
        if (remainder != 0) remainder :: acc
        else acc
    }
    
  loop(remaining = number.reverse, remainder = 1, acc = List.empty)
}

你可以这样使用:

plusOne(List(1, 2, 3))
// res: Number = List(1, 2, 4)

可以看到运行here的代码。

【讨论】:

  • 这是一个非常优雅的答案,但该函数需要一个 ListNode 并且必须返回一个 ListNode
  • @w33b 是的,我不喜欢使用 Leetcode 来学习 Scala,他们的大部分问题都旨在以可变的方式解决,但实际上并没有不要让您访问 stdlib,这对于学习使用在现实生活中没人会使用的奇怪的 ListNode 更为重要。
  • @w33b 将此答案与ListNode 一起使用并不难。与公认的答案相比,我更喜欢这个,因为它看起来更有效,看起来更直接。
  • @w33b Here's 改编版。它很乱,但似乎有效。
【解决方案2】:

这是一种方法:

class ListNode(_x: Int = 0, _next: ListNode = null) {
  var next: ListNode = _next
  var x: Int = _x
}

def toNode(l: List[Int]): ListNode = {
  l.foldRight[ListNode](null)((x, acc) => new ListNode(x, acc))
}

def toList(node: ListNode): List[Int] =
  Iterator.iterate(node)(_.next).takeWhile(_ != null).map(_.x).toList

def plusOne(head: ListNode): ListNode = {
  val l0 = toList(head)
  val l1 = List(1)
  val res = l0.reverse.zipAll(l1, 0, 0).foldLeft((List.empty[Int], 0)){
    case ((acc, c), (x0, x1)) =>
      val s = x0 + x1 + c
      if (s >= 10) (s-10 :: acc, 1) else (s :: acc, 0)
  }
  toNode(if (res._2 == 0) res._1 else 1 :: res._1)
}

请注意,fold 不是将 ListNode 元素转换为可能对大型 ListNode 造成问题的数字,而是用于执行逐元素迭代求和。

测试代码:

toList( plusOne( toNode(List(0)) ) )
// res0: List[Int] = List(1)

toList( plusOne( toNode(List(1, 2, 3)) ) )
// res1: List[Int] = List(1, 2, 4)

toList( plusOne( toNode(List(2, 9, 9)) ) )
// res2: List[Int] = List(3, 0, 0)

toList( plusOne( toNode(List(9, 9, 9)) ) )
// res3: List[Int] = List(1, 0, 0, 0)

【讨论】:

  • 好吧,既然你提到了性能,最好改用reverseItertator
  • 我的注意事项:大型 ListNodes 更多是为了避免数字溢出/舍入问题而不是性能。同意如果性能是优先事项,它可以进一步完善。
【解决方案3】:

不是很有效的解决方案,但肯定是最短的:

(List(1,2,3).mkString.toInt + 1).toString.toList.map(_.asDigit)

代码运行在Scastie

在 Scala 2.13 中,您可以使用 List.unfold:

def increaseLast(list: List[Int]): List[Int] = List.unfold(list) {
  case Nil =>
    None
  case x :: Nil =>
    Some((x + 1) % 10, Nil)
  case x :: xs if xs.forall(_ == 9) =>
    Some((x + 1) % 10, xs)
  case x :: xs =>
    Some(x, xs)
}

代码在Scastie 运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-17
    • 1970-01-01
    相关资源
    最近更新 更多