Codility刷题之旅 - Exercise3 - 2017 Contest

290 阅读6分钟

Exercise3 - 2017 Contest

TennisTournament: Given the numbers of players and available courts, calculate the maximum number of parallel tennis games.

image.png

问题概述:本题输入是两个正整数P和C,P代表了参加网球锦标赛的总球手数,C代表了比赛方拥有的网球场地的数量,需要返回的,是锦标赛中能同时进行的比赛数量。

解法概述:解法十分简单,同时进行的最多比赛数量除了受到比赛场地数(C)的约束,还要受到参加的球手数的约束(P//2),因此对两个值取min()即为最终的结果。

def solution(P, C):
    return min(P//2, C)

image.png

SocksLaundering: From drawers containing both clean and dirty socks, choose socks to launder in order to obtain the maximum number of clean pairs of socks.

image.png

问题概述:输入是一个正整数K,包含不超50个正整数的Array C和Array D。其中K代表一共可以洗多少只袜子,C代表了一筐干净袜子,D代表了一筐脏袜子。C、D中每个正整数的值,代表了袜子的颜色,最多也只有50种颜色。

需要返回的,是我们从脏袜子框里选洗了K只袜子后,我们最多能够拥有多少双干净袜子。

解法概述:本题解法上用到了贪婪算法,具体算法如下: 第一步,是在干净袜子筐里选能够成对的干净袜子,直接计入我们要返回的双数中, 第二步,是对剩余的只有一只的干净袜子,在脏袜子筐里找是否有相同颜色的单只袜子,然后不需要考虑其他问题,找到颜色符合的就直接洗,计入总干净袜子中。 第三步,是在还能继续洗2只袜子以上时,继续在脏袜子筐里找成对的脏袜子,无脑洗计入结果即可

def solution(K, C, D):
    result = 0
    # 先计算C中有多少对干净袜子,以及不成对的干净袜子
    clean_single = [0] * 51 # 最多有50种颜色
    for c in C:
        if clean_single[c]==1: # 干净袜子已经成对,提前计入result
            result += 1
            clean_single[c]=0
        else:
            clean_single[c]=1
    # 计算D中每种颜色的脏袜子有多少只
    dirty_socks = [0] * 51
    for d in D:
        dirty_socks[d] += 1
    # 开始判断K只袜子该洗什么颜色,具体步骤如下:
    # 1. 先根据clean_single中有单只干净袜子的颜色,判断D中是否有对应颜色脏袜子,有则一定洗一只进行配对(反证可证明)
    # 2. 完成1后,再看dirty_socks中每种脏袜子的颜色是否>1只,有则K余额足够情况下一定洗(反证可证明)
    # 3. 1-2的过程中直到K不足,或2过程已经找不到任何大于1的同颜色脏袜子,即可返回结果
    for i, (c,d) in enumerate(zip(clean_single, dirty_socks)):
        if K==0:
            break
        if c==1 and d>0:
            result += 1
            dirty_socks[i] -= 1
            K -= 1
    for i, d in enumerate(dirty_socks):
        if K<2:
            break 
        while d>=2 and K>=2:
            result += 1
            d -= 2
            K -= 2
    return result 

image.png

ArrayRecovery: Recover a broken array using partial information in another array.

image.png

问题概述:题干稍显复杂,还是要结合题中给出的例题才能更好理解:

Bob编写了一个程序来获得数组B,定义如下:

  • 对于每个索引J,寻找使得K < J和A[K] < A[J]成立的最大的K。然后令B[J] = A[K]。
  • 如果不存在这样的K,那么令B[J]=0。

也就是说,数组B的索引为J的元素就是数组A中,索引小于J的,最后一个出现的小于A[J]的值,如果没有这样的元素,则为0。

  • 例子1: A=[2,5,3,7,9,6],可得B=[0,2,2,3,7,3]。
  • 例子2: B[5]=3,因为A[5]是6,而A[5]之前小于6的最后一个值是3。

Bob得到了数组B,但是却误删了A。他现在打算找到可以生成B的所有可能A的个数。由于这个可能个数有可能非常大,只需要返回【可能个数mod(10^9+7)】

解法概述:本题解法中可行解数量的计算方法,借鉴了这篇Github里的结论,但min_arr,max_arr的计算,以及factorial_res中化简原计算公式这两部分就是自己另外推出来的了。

目前我没有办法把本题结果做到100%,因此min_arr和max_arr的推导上应该还有些问题。并且需要特别注意的是因为位数精度的问题,如果在factorial_res中不是像我现在这样导入的是Decimal类型的整数,那么在计算余数的过程中会出现误差。

我这里的max_arr和min_arr的确定方式也是用官方例题case一点一点试出来的,我还没完全想清楚本题的每一位取值的确定方式。如果有能做到更高正确度的兄弟,希望可以分享下解法。

# Samples:
# 1. ([0, 2, 3, 2, 2, 0, 0, 0], 4) -> 4
# 2. ([0, 0, 3], 5) -> 6
# 3. ([0], 1000000000) -> 
# 4. ([0, 0], 999999993) -> 91
import math 
import decimal

def factorial_res(result, M, N, div):
    # Count = [(n-1+m)!]/[(n-1)!*m!],n代表单独每位取值范围,m代表连续值的个数

    len_N = len(str(N))
    len_res = len(str(result))
    max_len = M * len_N + len_res + 1
    decimal.getcontext().prec = max_len

    begin = N+M-1
    while begin>N-1:
        result *= begin
        begin -= 1
    result /= math.factorial(M)
    if result>=div:
        return result % div
    return result

def solution(B, M):
    # 1. 如果B的取值更新了,则A中前一位是该值,且A中当前位数是大于前一位的
    # 2. 如果B的取值没更新,则A中当前位数值,是小于等于前一位值的(若是第一位,则在1~M中任意取值),同时大于B当前位数值
    max_arr = [M] * len(B)
    min_arr = [1] * len(B)
    # 正向更新一遍min_arr和max_arr
    for i, (now_b, next_b) in enumerate(zip(B[:-1], B[1:])): 
        if next_b>now_b: # b中元素递增,则可确定A中前一位的值
            max_arr[i] = next_b 
            min_arr[i] = next_b
            min_arr[i+1] = next_b + 1
        elif next_b==now_b:
            min_arr[i+1] = next_b + 1
            max_arr[i+1] = max_arr[i]
        else: # next_b<now_b, 一定是B中之前已经出现过的value,
            min_arr[i+1] = next_b + 1
            pre_v_index = B.index(next_b)
            max_arr[i+1] = B[pre_v_index+1]
            #max_arr[i+1] = # 比Array b中上一次出现next_b后的值都要小
    for i in range(len(B)-1,0,-1):
        if B[i-1]>=B[i] and min_arr[i-1]<min_arr[i]:
            min_arr[i-1]=min_arr[i]
    # print('B:', B)
    # print('max_arr:', max_arr)
    # print('min_arr:', min_arr)

    # 根据【相同取值的连续值】的位数,取值数量是不同的:
    # Count = [(n-1+m)!]/[(n-1)!*m!],n代表单独每位取值范围,m代表连续值的个数
    result = decimal.Decimal(1)
    M = 1  # 连续值的个数
    pre_min_v, pre_max_v = min_arr[0], max_arr[0]
    div = decimal.Decimal(1000000007)
    for min_v, max_v in zip(min_arr[1:]+[-1], max_arr[1:]+[-1]):
        if min_v==pre_min_v and max_v==pre_max_v:
            M += 1
        else:
            N = pre_max_v-pre_min_v+1 # 单独每位取值的个数
            if M>0: 
                # 对于大N,直接套公式计算会超时,要把(n-1-m)!和(n-1)!做一个消项
                result = factorial_res(result, M, N, div)
            M = 1
        # print(min_v, max_v, result, M)
        pre_min_v, pre_max_v = min_v, max_v
    return int(result)

image.png

DiamondsCount: Given points on a plane, count the number of sets of four points that form regular diamonds.

image.png image.png

问题概述:输入是两个有N个整数的等长Array X和Y,同index的元素两两配对代表了N个坐标点,需要返回的,是用这N个坐标点可以组成多少个Diamond。Diamond的定义,即四个点组成的四个边完全等长。

解法概述:根据Diamond的定义,我们不难推出Diamond的两个对角线一定是一个水平(有相同的y),一个垂直(有相同的x)。并且其垂直对角线两侧点的y,距离水平对角线一定相等且大于1.

follow这个思路,建立了y_dict和x_dict两个Dictionary,记录每一种x/y值下对应的y/x值的list。就比如例题中的case1在y=3这条水平线上有三个点,对应就会维护成y_dict中key=3下的一个长度为3的list,list中每个值对应点的x坐标。同样这三个点也会分别维护在x_dict中三个不同的key的list当中。

有了x_dict和y_dict后,就可以继续写搜寻符合条件的diamonds点的判断逻辑了。这里的逻辑我是从先确定X的思路入手的,具体逻辑参见solution中的四层循环部分,其实只要想清楚了判断逻辑,虽然有点绕但是最终写出来还是不难的。

def solution(X, Y):
    N = len(X)
    x_dict, y_dict = {}, {}
    for x,y in zip(X, Y):
        if x in x_dict:
            x_dict[x].append(y)
        else:
            x_dict[x] = [y]
        if y in y_dict:
            y_dict[y].append(x)
        else:
            y_dict[y] = [x]
    # X从左到右
    sorted_X = sorted(set(x_dict.keys()))
    result = 0
    for x in sorted_X:
        for y in x_dict[x]:
            cand_xs = y_dict[y]
            for corr_x in cand_xs:
                if corr_x>x and (corr_x-x)%2==0: # 只考虑x右侧的corr_x,避免重复计算
                    center_x = (x+corr_x)//2
                    y_dist_lst = [0] * N
                    if center_x in x_dict:
                        for cand_y in x_dict[center_x]:
                            y_dist = abs(cand_y - y)
                            if y_dist_lst[y_dist]==1:
                                result += 1
                            else:
                                y_dist_lst[y_dist]=1
    return result 

image.png

不过这个版本的Performance并未达到100%,看了下Report Analysis,发现是有一个max_one_line的case报了timeout。这个case是所有点都在一条水平线上,也就是x不同但是y都相同。

image.png

解决方案也比较好想到,就是仿照之前先确定X计算result部分的代码,补上先确定Y计算result思路的代码,两个分支的判断标准,就是x和y中哪个的distinct value更少:

def solution(X, Y):
    N = len(X)
    x_dict, y_dict = {}, {}
    for x,y in zip(X, Y):
        if x in x_dict:
            x_dict[x].append(y)
        else:
            x_dict[x] = [y]
        if y in y_dict:
            y_dict[y].append(x)
        else:
            y_dict[y] = [x]
    if len(x_dict)<=len(y_dict):
        # X从左到右
        sorted_X = sorted(set(x_dict.keys()))
        result = 0
        for x in sorted_X:
            for y in x_dict[x]:
                cand_xs = y_dict[y]
                for corr_x in cand_xs:
                    if corr_x>x and (corr_x-x)%2==0: # 只考虑x右侧的corr_x,避免重复计算
                        center_x = (x+corr_x)//2
                        if center_x in x_dict:
                            y_dist_lst = [0] * N
                            for cand_y in x_dict[center_x]:
                                y_dist = abs(cand_y - y)
                                if y_dist_lst[y_dist]==1:
                                    result += 1
                                else:
                                    y_dist_lst[y_dist]=1
    else:
        sorted_Y = sorted(set(y_dict.keys()))
        result = 0
        for y in sorted_Y:
            for x in y_dict[y]:
                cand_ys = x_dict[x]
                for cand_y in cand_ys:
                    if cand_y>y and (cand_y-y)%2==0:
                        center_y = (cand_y+y)//2
                        if center_y in y_dict:
                            x_dist_lst = [0] * N
                            for cand_x in y_dict[center_y]:
                                x_dist = abs(cand_x-x)
                                if x_dist_lst[x_dist]==1:
                                    result += 1
                                else:
                                    x_dist_lst[x_dist]=1
    return result 

image.png