Google Code Jam Qualification Round 2012

昨天是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
#...

这样效率会略有提升。最后一题,有点复杂,我选择了放弃,如果大家有什么好的思路和想法欢迎提出来,一起探讨。

你可能感兴趣的:(python,Google,input,import,Dictionary,combinations)