提问者:小点点

使用Scala的回文


我从CodeChef遇到了这个问题。该问题说明如下:

如果从左到右和从右到左读取时,正整数在十进制系统中的表示相同,则称为回文。对于不超过1000000位的给定正整数K,将大于K的最小回文的值写入输出。

我可以定义一个isPalin的方法如下:

def isPalindrome(someNumber:String):Boolean = someNumber.reverse.mkString == someNumber

我面临的问题是如何从初始给定的数字循环并中断并返回第一个回文,当整数满足isPalin的方法?还有,有没有更好(有效)的方法来编写isPalin的方法?

在这里得到一些指导会很棒


共3个答案

匿名用户

如果你有一个像123xxx这样的数字,你知道,其中一个xxx必须低于321——那么下一个palindrom是123321。

或者xxx在上面,那么3留不住了,124421得下一个。

这里有一些没有保证的代码,不是很优雅,但是中间有(多个)九的情况有点毛茸茸的(19992):

object Palindrome extends App {

def nextPalindrome (inNumber: String): String = {
  val len = inNumber.length ()
  if (len == 1 && inNumber (0) != '9') 
    "" + (inNumber.toInt + 1) else {
    val head = inNumber.substring (0, len/2)
    val tail = inNumber.reverse.substring (0, len/2)
    val h = if (head.length > 0) BigInt (head) else BigInt (0)
    val t = if (tail.length > 0) BigInt (tail) else BigInt (0)

    if (t < h) {
      if (len % 2 == 0) head + (head.reverse)
      else inNumber.substring (0, len/2 + 1) + (head.reverse)
    } else {
     if (len % 2 == 1) {
       val s2 = inNumber.substring (0, len/2 + 1) // 4=> 4
       val h2 = BigInt (s2) + 1  // 5 
       nextPalindrome (h2 + (List.fill (len/2) ('0').mkString)) // 5 + "" 
     } else {
       val h = BigInt (head) + 1
       h.toString + (h.toString.reverse)
     }
    }
  }
}

def check (in: String, expected: String) = {
  if (nextPalindrome (in) == expected) 
    println ("ok: " + in) else 
    println (" - fail: " + nextPalindrome (in) + " != " + expected + " for: " + in)
}
//
val nums = List (("12345", "12421"), // f
  ("123456", "124421"), 
  ("54321", "54345"), 
  ("654321", "654456"), 
  ("19992", "20002"),
  ("29991", "29992"),
  ("999", "1001"),
  ("31", "33"),
  ("13", "22"),
  ("9", "11"),
  ("99", "101"),
  ("131", "141"),
  ("3", "4")
)
nums.foreach (n => check (n._1, n._2))
println (nextPalindrome ("123456678901234564579898989891254392051039410809512345667890123456457989898989125439205103941080951234566789012345645798989898912543920510394108095"))

}

我想它也可以处理一百万位数的Int。

匿名用户

做反转不是最好的主意。最好从字符串的开头和结尾开始,逐个元素迭代和比较。即使第一个和最后一个元素不匹配,你也在浪费时间复制整个字符串并反转它。在一百万个数字的东西上,这将是一个巨大的浪费。

对于较大的数字,这比反向快几个数量级:

def isPalindrome2(someNumber:String):Boolean = {
  val len = someNumber.length;
  for(i <- 0 until len/2) {
    if(someNumber(i) != someNumber(len-i-1)) return false; 
  }
  return true;
}

甚至可能有一种更快的方法,基于镜像字符串的前半部分。我会看看我现在是否能得到它……

更新所以这应该会在几乎恒定的时间内找到下一个回文。没有循环。我只是把它划掉了,所以我相信它可以被清理掉。

def nextPalindrome(someNumber:String):String = {
  val len = someNumber.length;
  if(len==1) return "11";
  val half = scala.math.floor(len/2).toInt;
  var firstHalf = someNumber.substring(0,half);
  var secondHalf = if(len % 2 == 1) {
    someNumber.substring(half+1,len);
  } else {
    someNumber.substring(half,len);
  }

  if(BigInt(secondHalf) > BigInt(firstHalf.reverse)) {
    if(len % 2 == 1) {
      firstHalf += someNumber.substring(half, half+1);
      firstHalf = (BigInt(firstHalf)+1).toString;
      firstHalf + firstHalf.substring(0,firstHalf.length-1).reverse
    } else {
      firstHalf = (BigInt(firstHalf)+1).toString;
      firstHalf + firstHalf.reverse;
    }
  } else {
    if(len % 2 == 1) {
      firstHalf + someNumber.substring(half,half+1) + firstHalf.reverse;
    } else {
      firstHalf + firstHalf.reverse;
    }
  }
}

匿名用户

这是我能实现的最通用和最清晰的解决方案:
编辑:摆脱了BigInt的,现在计算百万位数需要不到一秒钟的时间。

def incStr(num: String) = {  // helper method to increment number as String
  val idx = num.lastIndexWhere('9'!=, num.length-1)
  num.take(idx) + (num.charAt(idx)+1).toChar + "0"*(num.length-idx-1)
}

def palindromeAfter(num: String) = {
  val lengthIsOdd = num.length % 2 
  val halfLength  = num.length / 2 + lengthIsOdd
  val leftHalf  = num.take(halfLength)               // first half of number (including central digit)
  val rightHalf = num.drop(halfLength - lengthIsOdd) // second half of number (also including central digit)      

  val (newLeftHalf, newLengthIsOdd) =  // we need to calculate first half of new palindrome and whether it's length is odd or even
    if (rightHalf.compareTo(leftHalf.reverse) < 0) // simplest case - input number is like 123xxx and xxx < 321
      (leftHalf, lengthIsOdd) 
    else if (leftHalf forall ('9'==))              // special case - if number is like '999...', then next palindrome will be like '10...01' and one digit longer
      ("1" + "0" * (halfLength - lengthIsOdd), 1 - lengthIsOdd)
    else                                           // other cases - increment first half of input number before making palindrome
      (incStr(leftHalf), lengthIsOdd)

  // now we can create palindrome itself
  newLeftHalf + newLeftHalf.dropRight(newLengthIsOdd).reverse
}