有段时间没写了,但是之前把JD剩余的题目基本还是过了一次,题目总体都比较简单,本次的解析选择了其中的5个题目,由于比较简单,就分析的简略些,留一些思考的空间。因为很久没有更新,最近还会和大家讨论一个动态规划解决的简单博弈问题。
站队
<题目来源: 京东2017秋招 原题链接-可在线提交(赛码网) >
问题描述
一条很长的队伍,队伍里面一共有n个人。所有的人分为三类:警察,小偷和普通人。将队伍里面的人从前到后由1到n编号,编号为i的人与编号为j的人的距离为i与j之差的绝对值。
每一个警察有一个能力值x,表示他能够监视与他距离不超过x的所有人,小偷被警察发现当且仅当他被一个或多个警察监视到。你知道在整条队伍中,一共有多少个小偷会被警察发现吗?
题目意思比较简单,最容易想到就是扫描一次这个队伍,如果是警察,就检查其能力范围内是否有小偷,如果有小偷就给小偷打上标记,这样是为了防止重复统计。当执行完本次扫描后就得到了最后的结果。
观察题目的数据规模,n<=100000,而每个警察能力值是1-9,最多需要扫描前后共计18个人。显然最坏情况下的运算也不过百万级,是完全可以在1s内出解的。
那么我们来思考下如果不限制警察的范围,那么这样做显然就不可行了。这样最坏情况可以是O(n^2),显然是可接受的。问题出在哪里呢?是因警察覆盖的范围出现了大量的重复。比如A警察在x=100的地方,B在x=110的地方,他们能力值都是100,显然对绝大多数位置进行了多次扫描。
如果要提高程序效率我们就要尽可能的减少重复的扫描,那么我们首先想到的是在加一个标记,表示当前扫描到了哪个位置,下个警察再进行扫描时,如果扫描到了该位置就不再继续扫描了。考虑下面的情况:
......------(1)------..............---(2)---........................
---------------------------(3)---------------------------
其中括号内的数字表示警察,"-"是其能力范围,..可以表示小偷或者普通人。
显然,记录最后扫描位置并不是一种好的方法。我们希望找到所以警察能力值不重复覆盖的范围,联系到线段覆盖问题,又或者是流水线作业问题等,我们得到一种处理方法,把每个警察和其能力范围看做是一条线段,把所有的这些线段按左端点排序,然后依次处理右侧的值。考虑当前线段右侧的位置和下条线段左侧的位置即可。
import sys
def main():
n = map(int, sys.stdin.readline().strip().split())[0]
line = list(map(str, sys.stdin.readline().strip().split())[0])
segments = []
for i, ch in enumerate(line):
if '1' <= ch <= '9':
t = int(ch) - int('0')
left = max(0, i - t)
right = min(n - 1, i + t)
segments.append((left, right))
segments = sorted(segments, key=lambda s: s[0])
#print segments
current = -1
res = 0
for segment in segments:
if segment[0] <= current:
l = current + 1
r = segment[1]
else:
l = segment[0]
r = segment[1]
for i in range(l, r + 1):
if line[i] == 'X':
res += 1
if r > current:
current = r
print res
if __name__ == '__main__':
main()
第K个幸运数
<题目来源: 京东2017实习生招聘 原题链接-可在线提交(赛码网) >
问题描述
4和7是两个幸运数字,我们定义,十进制表示中,每一位只有4和7两个数的正整数都是幸运数字。前几个幸运数字为:4,7,44,47,74,77,444,447...
现在输入一个数字K,输出第K个幸运数。
先按顺序列出一些幸运数(下划线是为了从低位开始对齐):
___4
___7
__44
__47
__74
__77
_444
_447
_474
_477
_744
_747
_774
_777
4444
...
观察规律发现:1位的2个,2位的4个,3位的8个,推测n位的幸运数有2^k个
再来观察低位规律 4, 7, 4, 7...
次低位的规律4, 4, 7, 7, 4, 4, 7, 7...
右侧第三位的规律,4, 4, 4, 4, 7, 7, 7, 7...
那么我们就可以依靠这些规律来计算每一位的数字,需要注意的是在计算第k位的时候,前k-1位是需要去掉的。
第t个幸运数从低位开始第i位上的数字是r = (t - 2^i - 2) % 2^i
当 1 <= r <= 2^i / 2 时为'4',否则为'7'
import sys
def main():
case = map(int, sys.stdin.readline().strip().split())[0]
for c in range(case):
n = map(int, sys.stdin.readline().strip().split())[0]
sum2s = 1
num = []
while True:
sum2s *= 2
if n - sum2s + 2 > 0:
r = (n - sum2s + 2) % sum2s
if 1 <= r <= sum2s / 2:
num.insert(0, '4')
else:
num.insert(0, '7')
else:
break
print ''.join(num)
if __name__ == "__main__":
main()
选举游戏
<题目来源: 京东2016实习生招聘 原题链接-可在线提交(赛码网) >
问题描述
小东和其他小朋友正在玩一个关于选举的游戏。选举是通过投票的方式进行的,得票最多的人将获胜。
小东是编号为1的候选者,此外还有其他的候选者参加选举。根据初步的调查情况,所有准备投票的小朋友都有一定的投票倾向性,小东如果要获得胜利,必须争取部分准备为其他候选人投票的小朋友。
由于小东的资源较为有限,她希望用最小的代价赢得胜利,请你帮忙计算她最少需要争取的选票数。
要小东能获胜,就要确保他自己的票超过另外所有人的票,就要从其他的候选者那里拿票。为了尽可能少的从其他的人手中拿票,那么应该尽可能的从比他多且最多的人手中拿票。注意到拿票是2个动作,小东会多一票,而被拿票的后选择会少一票。
问题就解法就是始终从票最多的人那里拿票(人会发生变化),直到自己的票数刚好成为最多的时候。
堆是一个能很好支撑这个要求的数据结构,如果不清楚堆(heap)可以查询相关资料。下面的代码使用了heapq来实现。
import sys
import heapq
def main():
while True:
line = map(int, sys.stdin.readline().strip().split())
if len(line) < 0:
break
n = line[0]
heap = []
line = map(int, sys.stdin.readline().strip().split())
for i, l in enumerate(line):
if i:
heapq.heappush(heap, -l)
cnt = 0
while True:
e = -heapq.nsmallest(1, heap)[0]
if line[0] > e:
break
line[0] += 1
cnt += 1
e -= 1
heapq.heapreplace(heap, -e)
print cnt
if __name__ == '__main__':
main()
交易清单
<题目来源: 京东2016实习生招聘 原题链接-可在线提交(赛码网) >
问题描述
金融证券行业超好的薪酬待遇,吸引了大批的求职者前往应聘,小东也不例外,准备应聘一家证券公司。面试官为考察她的数据分析、处理和编码能力,为她准备了以下问题。
股票交易中,委托是指股票交易者在证券公司买卖股票。每手委托包括一个委托单号i、价格pi、买入或卖出标记di及交易数量qi。
交易处理中,需要把同类业务(买入或卖出)中相同价格的所有委托合并起来,形成一个清单。清单的第一部分为按价格降序排列的合并后的卖出委托,紧随其后的是按相同顺序排列的买入合并委托。证券公司比较关心的是比较抢手的s条合并委托信息,需要得到买入及卖出最抢手的s条合并委托。对于买入委托,抢手的是指报价高的委托,而卖出委托中报价低的较为抢手。若买或卖的合并委托数小于s条,则全部列入清单中。
现在小东拿到的是n个委托,请你帮忙找出最抢手的s个合并委托。
这个题目本身不难,但是一定要读懂题意,该如何合并数据,该如何排序,如何取排序后的top n。这个题目可以锻炼快速编程的能力。
例如合并价格相同的委托,可以采用多种方式,我这里使用python,为了尽可能的快速,使用了dict。然后分别再对买入和卖出的委托进行排序。
import sys
def main():
while True:
line = map(int, sys.stdin.readline().strip().split())
if len(line) < 2:
break
n, s = line[0], line[1]
dict_b = {}
dict_s = {}
for i in range(n):
line = map(str, sys.stdin.readline().strip().split())
if line[0] == 'B':
r = dict_b.get(line[1])
if r is None:
dict_b[line[1]] = int(line[2])
else:
dict_b[line[1]] = r + int(line[2])
else:
r = dict_s.get(line[1])
if r is None:
dict_s[line[1]] = int(line[2])
else:
dict_s[line[1]] = r + int(line[2])
list_b = []
list_s = []
for k, v in dict_b.iteritems():
list_b.append((int(k), v))
list_b = sorted(list_b, reverse=True)
for k, v in dict_s.iteritems():
list_s.append((int(k), v))
list_s = sorted(list_s)
p = min(s, len(list_s))
for e in list_s[p::-1]:
print 'S', e[0], e[1]
p = min(s, len(list_b))
for e in list_b[:p]:
print 'B', e[0], e[1]
if __name__ == '__main__':
main()
进制均值
<题目来源: 京东2017秋招 原题链接-可在线提交(赛码网) >
问题描述
尽管是一个CS专业的学生,小B的数学基础很好并对数值计算有着特别的兴趣,喜欢用计算机程序来解决数学问题。现在,她正在玩一个数值变换的游戏。她发现计算机中经常用不同的进制表示同一个数,如十进制数123表达为16进制时只包含两位数7、11(B),用八进制表示时为三位数1、7、3。按不同进制表达时,各个位数的和也不同,如上述例子中十六进制和八进制中各位数的和分别是18和11。
小B感兴趣的是,一个数A如果按2到A-1进制表达时,各个位数之和的均值是多少?她希望你能帮她解决这个问题?
所有的计算均基于十进制进行,结果也用十进制表示为不可约简的分数形式。
这个题目考察进制转换,利用短除法可以计算任何进制的转换。此外,要掌握一到两种最大公约数(gcd)的计算方法。
import sys
def gcd(a, b):
c = a % b
if c == 0:
return b
return gcd(b, c)
def main():
while True:
n = map(int, sys.stdin.readline().strip().split())[0]
rest_sum = 0
for i in range(2, n):
temp = n
while temp:
rest_sum += temp % i
temp /= i
g = gcd(rest_sum, n - 2)
print("%d/%d" % (rest_sum / g, (n - 2) / g))
if __name__ == '__main__':
main()