阿里巴巴2018校园招聘运筹优化算法工程师编程题

来源:互联网 发布:淘宝类目哪个是免费的 编辑:程序博客网 时间:2024/05/29 08:37

对于考试向来都是后知后觉,过后留下一堆不甘与遗憾——然而不行就是不行,再接再厉。

声明:涉及阿里校招笔试,如有侵权,请联系我删除。

一、带信号灯的最短路问题

1. 题目描述

现在城市有N个路口,每个路口有自己的编号,从0到N-1,每个路口还有自己的交通控制信号,例如0,3表示0号路口的交通信号每3个时刻变化一次,即0到3时刻0号路口允许通过,3到6时刻不允许通过,而6到9时刻又允许通过;以此类推,所有路口的允许通行都从时刻0开始。同时城市中存在M条道路将这N个路口相连接起来,确保从一个路口到另一个路口都可达,每条路由两个端点加上通行所需要的时间表示。现在给定起始路口和目的路口,从0时刻出发,请问最快能在什么时刻到达?

2. 分析思路

如果没有信号灯。
要在网络中找到从 一点到另外一点的最短路径,我们可以使用现有的最短路算法,可参考http://blog.csdn.net/murmured/article/details/19281031。

在加入信号灯约束之后,要考虑到达中间某个路口后的等待时间,而等待时间与到达路口的时刻和路口信号灯变化周期有关。如果是按照Floyd算法来看,需要遍历每一个节点k,如果d[i, j] > d[i, k] + d[k, j]则更新d[i, j]。但是每一个d[i, j]都是考虑以i为起点j为终点计算的通行时间,所以应比较d[i, j]与d[i, k]+w[k, j]+从通过k路口后时刻算起到达j路口的时间。由此看来Floyd算法在本题行不通。

再考虑Dijkstra算法。题目要求计算的是从给定起点到终点的最短时间,这与Dijkstra算法计算其实节点到终节点的特性类似。现在回顾一下Dijkstra算法:

给定一个网络有节点集V,有向边集E,权重矩阵W,起点和终点分别为s和t。
初始化:标记节点集合为M,仅包含s;M中节点到V-M中节点的距离设为无穷大,初始节点到各标记节点的最短路径记为T,T[s]=0;
step1:检查标记节点中是否包含t,如果是则终止,否则进入step2;
step2:计算从M中各节点到V-M中各节点距离的最小值,找到V-M中对应最小值的节点并将其添加为标记节点,对应的最短距离为找到的最小值。用i遍历M中各节点,j遍历V-M中各节点,min = min{T[i] + W[i, j]}。

由于Dijkstra算法每一步计算的都是从初始点到达各标记几点之间的最短距离,这使得我们能够很容易计算在有信号灯的情况下通过路口需要在路口等待信号灯的时间,因此加入信号灯约束之后可对Dijkstra算法做如下改动即可:

给定一个网络有节点集V,有向边集E,权重矩阵W,起点和终点分别为s和t,各路口信号灯变化周期为C。
初始化:标记节点集合为M,仅包含s;M中节点到V-M中节点的距离设为无穷大,初始节点到各标记节点的最短时间径记为T,T[s]=0;
step1:检查标记节点中是否包含t,如果是则终止,否则进入step2;
step2:计算从M中各节点到V-M中各节点通行时间最小值,找到V-M中对应最短通行时间的节点并将其添加为标记节点,对应的最短通行时间为找到的最小值。用i遍历M中各节点,j遍历V-M中各节点,min = min{T[i] + W[i, j] + waittime[i, j]},其中waittime为从i路口到通过j路口需要等待信号灯时间,计算方法为:如果j为终节点则等待时间为0,否则计算m = (T[i] + W[i, j])%2*C[j],如果m小于C[j]则无需等待直接通过,否则需等待2C[j]-m。

3. 代码实现

# -*- coding: UTF-8 -*-.#!/bin/pythonimport numpy as npdef minTravelTime(N, intersections, M,roads, s, t):    # 初始化    # 初始点到各点的最短时间以及通过该路口需要等待的时间    dist = [float("inf") for i in range(N)]     # 初始点s到自己的距离为0      dist[s] = 0      # 最开始标记节点为初始节点     markNodes = [s]           wt = lambda x, y: 0 if divmod(x, 2*y)[1] < y else 2*y - divmod(x, 2*y)[1]    while t not in markNodes:        tmp_min = float("inf")        tmp_node = -1        for i in markNodes:            for j in range(N):                if j not in markNodes:                    if j == t:                        wait_time = 0                    else:                        wait_time = wt(dist[i] + roads[i, j], intersections[j])                    if dist[i] + roads[i, j] + wait_time <= tmp_min:                        tmp_node = j                        tmp_min = dist[i] + roads[i, j] + wait_time        markNodes.append(tmp_node)        dist[tmp_node] = tmp_min    return dist[t]_N = int(input())     # 路口总数_intersections = {}   # 路口信号灯时间间隔for i in range(0, _N):    x, y = input().split(',')    _intersections[int(x)-1] = int(y)_M = int(input())_roads = np.zeros((_N, _N))for j in range(0, _M):    u, v, w = input().split(',')    _roads[int(v), int(u)] = int(w)    _roads[int(u), int(v)] = int(w)for i in range(_N):    for j in range(_N):        if(_roads[i, j]) == 0:            _roads[i, j] = float("inf")print(_roads)# 输出任意起点到终点的最短到达时间for i in range(_N):    for j in range(_N):        if i != j:            print(i+1, j+1, minTravelTime(_N, _intersections, _M, _roads, i, j))# 以下为题目要求输出# _s, _t = input().split(',')# _s, _t = int(_s), int(_t)# print(str(minTime)+"\n")

4. 算例演示

(1)输入
6
0,16
1,17
2,15
3,17
4,18
5,14
9
0,1,66
0,2,80
0,3,50
1,4,78
1,5,90
1,2,30
2,3,45
2,5,81
4,5,76

(2)输出
1 2 66.0
1 3 80.0
1 4 50.0
1 5 146.0
1 6 158.0
2 1 66.0
2 3 30.0
2 4 75.0
2 5 78.0
2 6 90.0
3 1 80.0
3 2 30.0
3 4 45.0
3 5 112.0
3 6 81.0
4 1 50.0
4 2 90.0
4 3 45.0
4 5 180.0
4 6 141.0
5 1 144.0
5 2 78.0
5 3 108.0
5 4 165.0
5 6 76.0
6 1 168.0
6 2 90.0
6 3 81.0
6 4 135.0
6 5 76.0

二、仓库货架储位编号

1. 题目描述

菜鸟仓库的货架格子编号满足如下规律:第1个格子编号为1,第2-3个12,第4-6个格子编号为123……以此类推每个格子由一个0-9之间的整数编号,n个格子的编号连起来有如下规律1|12|123|……|123456789101112131415|……|12345678910111213141516…k,仓库中的储位一共有一千多万个。如何快速找到第K个格子对应的编号。

2. 分析思路

乍一看,以为第n行第k个格子的编号为k,然后求和S=1+2+3+…+n的和,即为前n行的格子数,再用K-S即为k的编码。其实然并卵,注意题目描述,“每个格子由一个0-9之间的整数编号”,例如12345678910这个序列中,第10个格子的编号为1,第11个格子的编号为0,所以乍一看的思路不对。

再细想,为了确定第K个格子的编号,有以下几点至关重要:一是格子的行号n,二是1+2+3+…+k共有多少个格子f(k),四是前n-1行一共有多少个格子snp。如果能找出f(k)的通项公式,然后对f(k)求前n项和,再看K位与那个区间内得到snp,再计算比K-snp小的f(k)和比K-snp大或相等的f(k+1),得到k,则K的编号即为k+1从左边数第K-snp-f(k)位对应的数。所以现在问题的关键变成计算一个给定的数有多少位。这个问题经过一番苦思冥想终究没想到怎么样用一个方便求和的通项公式表达,但是在程序里面用函数来时先却是非常的简单,在Python里面可以表达成len(str(k))。所以在这里思路一转,变成牺牲计算时间用迭代的方式进行求解,过程如下:

G(n):前n行格子的总数,f(n):第n行格子的总数
step1:n = 1, G(n) = 0;
step2:G(n) = G(n) + f(n),比较G(n)和K的大小,如果G(n)小于K,则n = n + 1,重复step2;否则返回n和snp = G(n)-f(n),分别是K位于的行号n和前n-1行格子数,k = 1;
step3:比较f(k)与K-snp的大小,如果f(k)小于K-snp则k = k + 1继续step3,否则返回k;
step4:K位于第n行的第k个数中的第K-snp-f(k-1)位数。

f(n)的计算方式如下:

def f(n):    if n == 0:        return 0    else:        return reduce(lambda x, y: x+y, list(map(lambda x: len(str(x)), range(1, n+1))))

3. 代码实现

# -*- coding: UTF-8 -*-.#!/bin/pythonfrom functools import reduce# 第n行格子数def f(n):    if n == 0:        return 0    else:        return reduce(lambda x, y: x+y, list(map(lambda x: len(str(x)), range(1, n+1))))def P(K):    n = 1    gn = f(n)    while gn < K:        n += 1        gn = gn + f(n)        # print(gn)    snp = gn - f(n)    k = 1    while f(k) < K-snp:        k += 1    return [n, list(str(k))[K-snp-f(k-1)-1]]ts = ''tn = 1for i in range(1, 1000):    [n, p] = P(i)    if n == tn:        ts += str(p)    else:        print(ts)        tn = n        ts = str(p)print(ts)

4. 算例演示

(1)输入:1-1000
(2)输出
1
12
123
1234
12345
123456
1234567
12345678
123456789
12345678910
1234567891011
123456789101112
12345678910111213
1234567891011121314
123456789101112131415
12345678910111213141516
1234567891011121314151617
123456789101112131415161718
12345678910111213141516171819
1234567891011121314151617181920
123456789101112131415161718192021
12345678910111213141516171819202122
1234567891011121314151617181920212223
123456789101112131415161718192021222324
12345678910111213141516171819202122232425
1234567891011121314151617181920212223242526
123456789101112131415161718192021222324252627
12345678910111213141516171819202122232425262728
1234567891011121314151617181920212223242526272829
123456789101112131415161718192021222324252627282930
12345678910111213141516171819202122232425262728293031
1234567891011121314151617181920212223242526272829303132
123456789101112131415161718192021222324252627282930313233
12345678910111213141516171819202122232425262728293031323334
1234567891011121314151617181920212223242526272829303132333435
123456789101112131

阅读全文
1 0