问题描述
我在2008年6月写了一篇随笔“可以使用C#语言的在线ACM题库”,其中提到 Sphere Onlile Judge (SPOJ) 网站。现在我们来看看该网站 SPOJ Problem Set (classical) 中的“5. The Next Palindrome”。这道题目的主要内容如下所示:
The Next Palindrome: A positive integer is called a palindrome if its representation in the decimal system is the same when read from left to right and from right to left. For a given positive integer K of not more than 1,000,000 digits, write the value of the smallest palindrome larger than K to output. Numbers are always displayed without leading zeros.
Input: The first line contains integer t, the number of test cases. Integers K are given in the next t lines.
Output: For each K, output the smallest palindrome larger than K.
Warning: large Input/Output data, be careful with certain languages
Time limit: 9s
简单地说,就是要找出比指定的数大的最小回文数(palindrome)。问题是这个数可能包含有百万个十进制数字,而时间限制是九秒,所以算法不能太低效。比如用最笨的办法,使用 BigInteger ,从指定的数开始逐个往上加一,然后判断是不是回文数,肯定是会超时的。
C# 语言解答
下面就是求解这个问题的 C# 程序:
01: using System; 02: using System.IO; 03: 04: namespace Skyiv.SphereOnlineJudge 05: { 06: // http://www.spoj.pl/problems/PALIN/ 07: class Palindrome 08: { 09: static void Main() 10: { 11: new Palindrome().Run(Console.In, Console.Out); 12: } 13: 14: void Run(TextReader reader, TextWriter writer) 15: { 16: for (var i = int.Parse(reader.ReadLine()); i > 0; i--) Out(writer, reader.ReadLine()); 17: } 18: 19: void Out(TextWriter writer, string s) 20: { 21: var n = s.Length / 2; 22: if (IsWhole9s(s)) Out(writer, s.Length - 1); 23: else if (s.Length == 1) writer.WriteLine((char)(s[n] + 1)); 24: else if (IsLeftReverseLargerThanRight(s)) Out(writer, s, n, false, s[n]); 25: else if (s.Length % 2 != 0 && s[n] < '9') Out(writer, s, n, false, (char)(s[n] + 1)); 26: else Out(writer, s, n - GetLeftSuccessive9sCount(s), true, '0'); 27: } 28: 29: void Out(TextWriter writer, int length) 30: { 31: writer.Write('1'); 32: writer.Write(new string('0', length)); 33: writer.WriteLine('1'); 34: } 35: 36: void Out(TextWriter writer, string s, int length, bool isNext, char center) 37: { 38: var zeros = new string('0', s.Length / 2 - length); 39: var c = (char)(s[length - 1] + (isNext ? 1 : 0)); 40: writer.Write(s.Substring(0, length - 1)); 41: writer.Write(c); 42: writer.Write(zeros); 43: if (s.Length % 2 != 0) writer.Write(center); 44: writer.Write(zeros); 45: writer.Write(c); 46: writer.WriteLine(GetLeftReverse(s, length - 1)); 47: } 48: 49: bool IsWhole9s(string s) 50: { 51: foreach (var c in s) if (c != '9') return false; 52: return true; 53: } 54: 55: bool IsLeftReverseLargerThanRight(string s) 56: { 57: var n = s.Length / 2; 58: var k = s.Length % 2 + n; 59: for (var i = 0; i < n; i++) 60: { 61: var cmp = s[k + i].CompareTo(s[n - 1 - i]); 62: if (cmp < 0) return true; 63: if (cmp > 0) return false; 64: } 65: return false; 66: } 67: 68: char[] GetLeftReverse(string s, int length) 69: { 70: var array = new char[length]; 71: for (var i = 0; i < length; i++) array[i] = s[length - 1 - i]; 72: return array; 73: } 74: 75: int GetLeftSuccessive9sCount(string s) 76: { 77: var i = s.Length / 2 - 1; 78: while (i >= 0 && s[i] == '9') i--; 79: return s.Length / 2 - 1 - i; 80: } 81: } 82: }
在该网站上提交后,运行通过,运行时间是 0.58 秒,占用内存为 22 MB,目前在 C# 语言中排名第三位。
该网站使用的 C# 编译器是 Mono C# compiler version 2.0.1,有点过时了。Mono C# 编译器目前最新版本是 2.6.4。
所用算法
将问题分为以下五种情况(对应 C# 源程序第 22 到 26 行):
- 如果所给的数全部由 9 组成,则输出 10…01 ,这里 0 的个数比 9 的个数少一个。(第 22 行,第 49 到 53 行的 IsWhole9s 方法,第 29 到 34 行的 Out 方法)
- 否则,如果所给的数只有一位数,则输出下一个数字。(第 23 行)
- 否则,如果所给的数的左半边倒转过来大于右半边,则以左半边为基准输出回文数。(第 24 行,第 55 到 66 行的 IsLeftReverseLargerThanRight 方法,第 36 到 47 行的 Out 方法,第 68 到 73 行的 GetLeftReverse 方法)
- 否则,如果所给的数的位数是奇数,并且最中间一位数字小于 9 ,则将最中间一位数字加一,并以左半边为基准输出回文数。(第 25 行,第 36 到 47 行的 Out 方法)
- 否则,计算所给的数的左半边从低位算起的连续的 9 的个数,这些连续的 9 需要替换为 0 ,然后再将左半边最右侧的非 9 数字加一,以此为基准输出回文数。如果所给的数的位数是奇数,则最中间一位数字必定是 9,这个 9 也必须替换为 0 。(第 26 行,第 75 到 80 行的 GetLeftSuccessive9sCount 方法,第 36 到 47 行的 Out 方法)
除了第一种情况,所求的回文数的位数是和所给的数的位数是一样的。举例如下:
情况 | 1 | 2 | 3 | 4 | 5 | ||||
---|---|---|---|---|---|---|---|---|---|
输入 | 9 | 99.99 | 7 | 21.07 | 38756 | 1234567 | 123.456 | 45699.99654 | 1239456 |
输出 | 11 | 10001 | 8 | 21.12 | 38783 | 1235321 | 124.421 | 45700.00754 | 1240421 |
上表中的小数点只是为了标出数的最中间位置,请忽略之。
C 语言解答
将前面的 C# 程序翻译为 C 程序,如下所示:
01: #include <stdio.h> 02: #include <stdlib.h> 03: #include <string.h> 04: 05: #define MAX_DIGITS 1000000 06: 07: static char buf[MAX_DIGITS / 2]; // for getString and getLeftReverse 08: 09: const char* getString(int c, int length) 10: { 11: memset(buf, c, length); 12: buf[length] = '\0'; 13: return buf; 14: } 15: 16: const char* getLeftReverse(const char* s, int length) 17: { 18: for (int i = 0; i < length; i++) buf[i] = s[length - 1 - i]; 19: buf[length] = '\0'; 20: return buf; 21: } 22: 23: void out(char* s, int len, int length, int isNext, char center) 24: { 25: const char* zeros = getString('0', len / 2 - length); 26: char c = s[length - 1] + isNext; 27: s[length - 1] = '\0'; 28: fputs(s, stdout); 29: putchar(c); 30: fputs(zeros, stdout); 31: if (len % 2 != 0) putchar(center); 32: fputs(zeros, stdout); 33: putchar(c); 34: fputs(getLeftReverse(s, length - 1), stdout); 35: putchar('\n'); 36: } 37: 38: void out101(int length) 39: { 40: putchar('1'); 41: fputs(getString('0', length / 2), stdout); 42: fputs(getString('0', length / 2), stdout); 43: if (length % 2 != 0) putchar('0'); 44: putchar('1'); 45: putchar('\n'); 46: } 47: 48: int isWhole9s(const char* s) 49: { 50: for (int i = 0; s[i] != '\0'; i++) if (s[i] != '9') return 0; 51: return 1; 52: } 53: 54: int isLeftReverseLargerThanRight(const char* s, int length) 55: { 56: int n = length / 2, k = length % 2 + n; 57: for (int i = 0; i < n; i++) 58: { 59: int cmp = s[k + i] - s[n - 1 - i]; 60: if (cmp < 0) return 1; 61: if (cmp > 0) return 0; 62: } 63: return 0; 64: } 65: 66: int getLeftSuccessive9sCount(const char* s, int len) 67: { 68: int i = len / 2 - 1; 69: while (i >= 0 && s[i] == '9') i--; 70: return len / 2 - 1 - i; 71: } 72: 73: int main() 74: { 75: char s[MAX_DIGITS + 1]; 76: int cnt = atoi(fgets(s, MAX_DIGITS, stdin)); 77: while (cnt-- > 0) 78: { 79: fgets(s, MAX_DIGITS, stdin); 80: int len = strlen(s); 81: s[--len] = '\0'; 82: int n = len / 2; 83: if (isWhole9s(s)) out101(len - 1); 84: else if (len == 1) printf("%c\n", s[0] + 1); 85: else if (isLeftReverseLargerThanRight(s, len)) out(s, len, n, 0, s[n]); 86: else if (len % 2 != 0 && s[n] < '9') out(s, len, n, 0, s[n] + 1); 87: else out(s, len, n - getLeftSuccessive9sCount(s, len), 1, '0'); 88: } 89: return 0; 90: }
在该网站提交后,运行通过,运行时间是 0.09 秒,占用内存为 3.0 MB,目前在 C99 strict 语言中排名第八位。
该网站使用的 C 编译器是 gcc 4.3.2,有点过时了。gcc 目前最新版本是 4.5.0。
Ruby 语言解答
将前面的 C# 程序翻译为 Ruby 程序,如下所示:
def is_whole_9s s
s.each_char do |c|
return false if c != ?9
end
true
end
def is_left_reverse_larger_than_right s
n = s.length / 2
s[0, n].reverse > s[s.length % 2 + n, n]
end
def get_left_successive_9s_count s
i = s.length / 2 - 1
i -= 1 while i >= 0 && s[i] == ?9
s.length / 2 - 1 - i
end
def out s, length, isNext, center
zeros = '0' * (s.length / 2 - length)
prefix = s[0, length - 1]
c = s[length - 1]
c = c.succ if isNext
print prefix
print c
print zeros
print center if s.length % 2 != 0
print zeros
print c
puts prefix.reverse
end
$stdin.gets
$stdin.each_line do |s|
s.chop!
n = s.length / 2;
case
when is_whole_9s(s) then puts "1#{'0' * (s.length - 1)}1"
when s.length == 1 then puts s[0].succ
when is_left_reverse_larger_than_right(s) then out s, n, false, s[n]
when s.length % 2 != 0 && s[n] < ?9 then out s, n, false, s[n].succ
else out s, n - get_left_successive_9s_count(s), true, ?0
end
end
在该网站提交后,运行通过,运行时间是 1.54 秒,占用内存为 6.6 MB,目前在 Ruby 语言中排名第十八位。
该网站使用 Ruby 1.9.0,有点过时了。Ruby 目前最新版本是 1.9.1。
总结
这三种语言的运行情况如下表:
语言 | 运行时间 | 内存占用 | 源程序行数 |
---|---|---|---|
C | 0.09 秒 | 3.0 MB | 90 |
C# | 0.58 秒 | 22 MB | 82 |
Ruby | 1.54 秒 | 6.6 MB | 44 |
可以看出,C 语言最快,而且最省内存。C# 语言比较慢,最费内存。Ruby 语言最慢,内存占用居中。