昨天是Google Code Jam的Qualification Round,我也参加了,可能是最近代码写的比较少,出了一些小错误,再加上觉得最后一题比较麻烦就放弃了,最后的了45分,貌似得分有20分就可以进入下一轮了。下面就来分享一下我的代码。
Problem A的题目叫Speaking in Tongues,题目大意是说Google有一种叫做Googlerese的语言,这种语言可以和英语相互转换,每种字母都有唯一的映射,可能是字母本身,也可能是其他字母。需要输入Googlerese语,然后转换成英语输出。
输入时在第一行输入一个数字,代表测试的次数,然后每一行是一句Googlerese语,可能由一个或多个'a'-'z'组成的单词组成,用空格分隔。输出时要采用“Case #n: english”这样的模板,n从1开始。为了防止题目过于简单,Google并没有提供所有字母之间的映射,而是提供了一个hint和一组sample。先根据hint和sample获取其中一部分映射:
#!/usr/bin/env python2.7 # -*- coding: utf-8 -*- sample_g = ( 'ejp mysljylc kd kxveddknmc re jsicpdrysi', 'rbcpc ypc rtcsra dkh wyfrepkym veddknkmkrkcd', 'de kr kd eoya kw aej tysr re ujdr lkgc jv' ) sample_e = ( 'our language is impossible to understand', 'there are twenty six factorial possibilities', 'so it is okay if you want to just give up' ) dic = {'a': 'y', 'o': 'e', 'z': 'q'} for i in xrange(3): for j in xrange(len(sample_g[i])): if sample_g[i][j] not in dic: dic[sample_g[i][j]] = sample_e[i][j] from pprint import pprint pprint(dic)
这样就会得到:
{' ': ' ', 'a': 'y', 'b': 'h', 'c': 'e', 'd': 's', 'e': 'o', 'f': 'c', 'g': 'v', 'h': 'x', 'i': 'd', 'j': 'u', 'k': 'i', 'l': 'g', 'm': 'l', 'n': 'b', 'o': 'e', 'p': 'r', 'r': 't', 's': 'n', 't': 'w', 'u': 'j', 'v': 'p', 'w': 'f', 'x': 'm', 'y': 'a', 'z': 'q'}
然后你可以发现在key中没有'q',在value中没有z,根据题目,将这一对键/值对加入dictionary中正好可以得到一个完整的映射。于是就可以得到第一题的答案:
#!/usr/bin/env python2.7 #-*- coding: utf-8 -*- mapping = {' ': ' ', 'a': 'y', 'b': 'h', 'c': 'e', 'd': 's', 'e': 'o', 'f': 'c', 'g': 'v', 'h': 'x', 'i': 'd', 'j': 'u', 'k': 'i', 'l': 'g', 'm': 'l', 'n': 'b', 'o': 'e', 'p': 'r', 'q': 'z', 'r': 't', 's': 'n', 't': 'w', 'u': 'j', 'v': 'p', 'w': 'f', 'x': 'm', 'y': 'a', 'z': 'q'} n = int(raw_input()) for i in xrange(1, n+1): print "Case #%d: %s" % (i, ''.join([mapping[ch] for ch in raw_input()]))
不过很可惜,这个题目明明很简单,但是我不知道当时怎么就手贱了,把'q'的映射搞错了,结果提交了四次都是incorrect,被罚了不少时间,郁闷阿。
接下来我觉得problem C似乎很简单,所以我先做的Problem C。Problem C的题目大意是说一个数值对,其中一个数通过循环位移可能得到另一个数。输入n组范围[a, b],然后在a<=m<n<=b的情况下找出所有满足上诉情况的数值对(m, n)的个数。采用Problem A的输出模板。
因为我选择使用python作为编程语言,同时python提供slice,所以我就想将数字转换成string,然后通过slice重组数字进行比较,这样应该可以得到结果。确实这一步的思路没有问题,但是我犯了一个很愚蠢的错误,使用暴搜,采用
for x in xrange(a, b+1): for y in xrange(x, b+1): if is_recycled(x, y): count += 1
这种非常低效的算法,于是我的到的结果就是,在进行small input的时候还不觉得很慢,可是在进行large input的时候在8分钟的限期内没有完成搜索,同时large input只有一次机会,而我当时不知道,非常可惜的失去了15分。后来我痛定思痛,决定把这题重新写一编,于是就有了初步的代码:
#!/usr/bin/env python #-*- coding: utf-8 -*- n = int(raw_input()) for i in xrange(n): lower, upper = raw_input().split() lower, upper = int(lower), int(upper) count = 0 for x in xrange(lower, upper + 1): str_x = str(x) for j in xrange(1, len(str_x)): y = int(str_x[j:] + str_x[:j]) if x < y <= upper: count += 1 print "Case #%d: %d" % (i + 1, count)
这段代码的思想是将一个在范围之内的数字进行转换,然后判断转换之后的数字是否符合要求,如果是则计数加一,否则继续判断下一个。可是这段代码有问题,一个非常严重的问题,有些数值对会被重复的计数。例如1212会在j=1和j=3的情况下都转换成2121,这样就被重复计数了。如果在计数后加一个break呢?显然这样子是不对的,因为一个数可以对应多个数,例如:[1111, 2222],其中1234就可以和4123, 3412,2341组成3对数值对。因此在计数后加一是绝对不行的。
如果在计数之后加一不行,那该怎么办呢?那就只有在遇到重复的数字的时候不进行计数了,于是我就想到了set,也因此有了下面的代码:
#!/usr/bin/env python #-*- coding: utf-8 -*- n = int(raw_input()) for i in xrange(n): lower, upper = raw_input().split() lower, upper = int(lower), int(upper) count = set() for x in xrange(lower, upper + 1): str_x = str(x) for j in xrange(1, len(str_x)): y = int(str_x[j:] + str_x[:j]) if x < y <= upper: count.add((x, y)) print "Case #%d: %d" % (i + 1, len(count))
这段代码能够正确的完成Problem C,不过由于使用数字与字符串之间的转换会比较耗时。因此在large input的时候会比较慢。Google给出的算法是一个不错的提高效率的方法,我在代码中加了一点注释:
int solve(int A, int B) { int power = 1, temp = A; // 计算数量级 while (temp >= 10) { power *= 10; temp /= 10; } int ret = 0; for (int n = A; n <= B; ++n) { temp = n; while (true) { temp = (temp / 10) + ((temp % 10) * power); // 将数字的最低位移动到最高位 if (temp == n) break; if (temp > n && temp >= A && temp <= B) ret++; } } return ret; }
其实更好的提高效率的方法是使用一门更高效的语言,例如C。针对Google给出的这个算法,CPP只需要几秒,而python却耗时约一分半
接下来是Problem B。题目大意我就不叙述了,根据题目的我并没有多考虑,直接就开始判断人数:
#! /usr/bin/env python2.7 # -*- coding: utf-8 -*- def run(n): data = raw_input().split() num, s, best = int(data[0]), int(data[1]), int(data[2]) scores = [int(x) for x in data[3:]] count = 0 for i in xrange(num): lower = scores[i] // 3 for x in xrange(lower-1, 11): for y in xrange(x, 11): for z in xrange(y, 11): if z - y > 2 or z - x > 2 or y - x > 2: continue if x + y + z == scores[i] and z >= best: if z - y == 2 or z - x == 2 or y - x == 2: if s: s = s - 1 else: continue count += 1 print "Case #%d: %d" % (n + 1, count) n = int(raw_input()) [run(i) for i in xrange(n)]
但是这样会导致重复计数,例如sample中的第三组数据,8可以分解成(2,2,4)和(2,3,3),这两组数据都满足要求,但是却只能计数为1。于是我就想改一改代码,首先是觉得循环里面有个三重循环很不爽,后来想起来itertools中提供了排列组合的函数,可以直接使用,于是就将三重循环给为了combinations,后来又发现使用combinations不能里出重复的组合于是换用combinations_with_replacement,这样一来就可以在count之后添加一个break,重复计数的问题就迎刃而解了。同时我还发现lower直接减一并不好,这样可能会出现负数,因此对代码进行了一份修改,于是就有了下列代码:
! /usr/bin/env python2.7 # -*- coding: utf-8 -*- from itertools import combinations_with_replacement def run(n): data = raw_input().split() num, s, best = int(data[0]), int(data[1]), int(data[2]) scores = [int(x) for x in data[3:]] count = 0 for i in xrange(num): lower = scores[i] // 3 lower = lower - 1 if lower != 0 else 0 for x, y, z in combinations_with_replacement(xrange(lower, 11), 3): if z - y > 2 or z - x > 2 or y - x > 2: continue if x + y + z == scores[i] and z >= best: if z - y == 2 or z - x == 2 or y - x == 2: if s: s = s - 1 else: continue count += 1 break print "Case #%d: %d" % (n + 1, count) n = int(raw_input()) [run(i) for i in xrange(n)]
这样子,似乎所有问题就解决了,可是当我测试sample的时候问题又出现了。在第一组数据中,只有一组surprising,在计算15的时候,会从(4,4,4)开始,当碰到(4,5,6)的时候s减一,计数加一,并跳出当前循环,进行下一个总分的判断,在计算13的时候在碰到(4,4,5)时会计数加一,跳出循环,进行下一个总分的判断。在计算11的时候会从(2,2,2)开始,...(3,3,4),(3,3,5)...可是这里的(3,3,5)并不能被计数,因为s已经为0了。所以我就想,其实s的个数于具体是那组数为surprising其实也不是毫无关系,如果总数比较小,被选中的可能性似乎就会相对变小,因为p不可能太小,所以先对总分小的数据进行筛选也许可行。于是我又一次更改代码:
! /usr/bin/env python2.7 # -*- coding: utf-8 -*- from itertools import combinations_with_replacement def run(n): data = raw_input().split() num, s, best = int(data[0]), int(data[1]), int(data[2]) scores = sorted([int(x) for x in data[3:]]) count = 0 for i in xrange(num): lower = scores[i] // 3 lower = lower - 1 if lower != 0 else 0 for x, y, z in combinations_with_replacement(xrange(lower, 11), 3): if z - y > 2 or z - x > 2 or y - x > 2: continue if x + y + z == scores[i] and z >= best: if z - y == 2 or z - x == 2 or y - x == 2: if s: s = s - 1 else: continue count += 1 break print "Case #%d: %d" % (n + 1, count) n = int(raw_input()) [run(i) for i in xrange(n)]
虽然我不确定我的想法是否正确,但是提交之后确实正确了。另外upper也可以修改一下,可以将范围缩小,例如sample中的第3组数据,如果从1-11来排列组合就组合就太多了,因此可以添加一句:
#... upper = lower + 4 if lower < 7 else 11 #...
这样效率会略有提升。最后一题,有点复杂,我选择了放弃,如果大家有什么好的思路和想法欢迎提出来,一起探讨。