京东实习生招聘题目解析(一):双端队列的应用

由于前几天开始随意作了一个JD的题目,因而就打算逐步把JD在赛码网上的5-3星难度的题目作完,比较有价值的题目会有比较详细的分析过程,其他的就阐述重点或简单带过便可。由于5星难度题目只有两个,就倒着来作好了。
此次的解析包含了3个4星难度题目,而且从其中一个题目来介绍双端队列的概念和使用。算法


保卫方案

<题目来源: 京东2017秋招 原题连接-可在线提交(赛码网)>数组

问题描述

战争游戏的相当重要环节就要到来了,此次的结果将决定王国的生死存亡,小B负责首都的防卫工做。首都处于一个四面环山的盆地中,周围的n个小山构成一个环,做为预警措施,小B计划在每一个小山上设置一个观察哨,日夜不停的瞭望周围发生的状况。
一旦发生外敌入侵事件,山顶上的岗哨将点燃烽烟。若两个岗哨所在的山峰之间没有更高的山峰遮挡且二者之间有相连通路,则岗哨能够观察到另外一个山峰上的烽烟是否点燃。因为小山处于环上,任意两个小山之间存在两个不一样的链接通路。知足上述不遮挡的条件下,一座山峰上岗哨点燃的烽烟至少能够经过一条通路被另外一端观察到。对于任意相邻的岗哨,一端的岗哨必定能够发现一端点燃的烽烟。
小B设计的这种保卫方案的一个重要特性是可以观测到对方烽烟的岗哨对的数量,她但愿你可以帮她解决这个问题。网络

图片描述

先准确理解题意:在一个环上有若个点,每一个点都和它相邻的两个点连通,选定其中任何两个点a和b,若是a从顺时针或者逆时针方向到b所通过的点没有比a和b中较小的点更高时(注意若是3个点的高度相等时候,是能够相互观察的),a和b能够相互观察。最终咱们须要知道有多个这样的(a, b)对数据结构

实际上,最简单粗暴的办法就是3一个三重循环进行扫描,首先2重循序去枚举一个观察对(a, b),再用一重循环检查(a, b)中有没更高的山就能够了。
好消息是这个题目数据很弱,这个O(n^3)的算法彻底能够经过测试,赛码网上的大多数人也是用的这个方法。
坏消息是这并非一个很好方法,若是按题目给定的数据规模是没法经过的。app

接下来咱们来观察这样一个O(n^3)的算法的执行过程,并从中找到一些能够优化的地方。首先,在不考虑环而且咱们选定顺时针方向的状况下,咱们计算(a, b)是否能够相互观察,咱们会扫描mount[a + 1], mount[a + 2], ... mount[b - 1],在扫描的过程当中若是发现第一个mount[i] > mount[a],那么此后任何点都不能被a观察到。学习

a.若是咱们在扫描的过程当中,发现mount[a + 1] > mount[a],那么a就不能观察到后续的任何点,也就没有必要对后面的点再进行扫描后了。接下来,若是mount[a + 2] > mount[a + 1],这个过程是一样的,mount[a + 1]一样没法观察到以后的任何一个点,一样不须要扫描。若是整个mount[i]是一个单调递增的序列,显然除了相邻的两个点能够观察,其他任何两个点是没法观察的(这里不暂考虑环的状况)。
b.若是mount[a + 1] < mount[a],那么mount[a]就有可能观察到a + 1后面的点,所以咱们须要再看接下来的状况,这个时候假设mount[a + 2] > mount[a + 1],显然是mount[a]能够观察到mount[a + 2],而mount[a + 1]不能再观察到后续的任何点(规则a说明),假设mount[a + 2] < mount[a + 1],那么mount[a + 2]也可能观察到后面的点,须要继续看接下来状况。测试

综合上面2点规则,咱们须要维护一个不递增(注意,是不递增,而不是递减,这就说明了有相等的状况)这样一个单调性的线性结构,当向这个线结构中加入一个mount到尾部时,咱们就从尾部一个mount一个mount的和当前加入的这个进行比较,如尾部的这个mount < 当前加入的,就须要将这个尾部mount移除,继续这个比较,直到线性结构中的某个mount >= 当前加入的,或者整个线性结构已经移除了全部的mount。还应当注意到,新加入mount和出队的全部mount都是能够相互观察,此外,相邻的两个mount能够相互观察。优化

如今咱们须要一个支撑咱们实现上述操做的数据结构--双端队列(deque),双端队列是一个能够在队列的任何一端均可以进行入队和出队操做的一个数据结构。对这个在题目而言,咱们在队首进行出队操做(一会在解决环的问题的时候会用到),在对尾进行入队和出队操做。spa

图片描述

接下来,须要解决的就是环的问题。解决环的方式通常能够采用将原来的环切段,而且从切断位置再复制一份这个断掉的环(也就是一根链了),还有就是取模的形式。均可以。对于这个问题,咱们只须要从当前位置考虑到后面n个位置便可。例如,如今n=5 共有5个mount,在处理mount[3]的时候,咱们须要向后考察mount[4],mount[5],而后是mount[1],最后是mount[2]。也就是,双端队列中只须要保留最多n个的mount,第n+1个加入的时候,须要将第1个从队首出队。再来观察个例子,如今n=5,且这个5个mount的高度都是不单调不增加的,当n=5进入双端队列时,队列中有5个元素,当第6个mount入队时(其实是第一个),显然就产生了重复,1不须要再观察到本身,更不须要绕过本身一圈再观察后面的mount,因此,此时应该将1从队首中出队。设计

至于题目任何两个mount能够从两个通路观察(顺时针或者逆时针),其实已经不须要处理,由于咱们都从任何1个点向一个方向观察了n个长度,从mount[a]若是能够观察mount[b],也就是从mount[b]能够观察mount[a],也就是咱们在处理a时考察了a->b是否能够观察,在处理b时,考察了b->a是否能够观察。因为可能会有重复的状况,所以咱们须要设置一个set类型(而不是obv[][]这样一个二维数组来标记,要知道题目的数据规模是n=10^6)来保存从任何一点能观察的其余的点,这样的存储空间也比较理想。

至此,问题尚未解决。还有一个相等的问题须要处理,也就是一样高的mount,由于题目是要求两个mount以前没有更高的mount就能够观察。由于3个或者更多相等的mount中,只要没有更高的,也是能够相互观察的。这部分咱们在双端队列中处理时,若是遇到相等,要继续向前找到不相等的。可是不要将这些mount出队

至于算法复杂度,前面我简单到提到过,尽管下面的代码也使用了3层的循环,但要注意实际的执行次数,任何一个元素在双端队列中最多被入队一次,出队一次(不考虑重复的状况)。

import sys

const_val = 0
const_num = 1

result = 0


def add_pair(a, b, pair):
    global result
    if a > b:
        a, b = b, a

    if b not in pair[a]:
        pair[a].add(b)
        # print a + 1, b + 1
        result += 1


def main():
    global result

    while True:
        result = 0
        h = []
        deque = []

        line = map(int, sys.stdin.readline().strip().split())
        if len(line) < 1:
            return
        n = line[0]
        line = map(int, sys.stdin.readline().strip().split())
        for elem in line:
            h.append(elem)

        pair = [set() for i in range(n)]
        for i in range(2 * n):
            seq = i if i < n else i - n

            if len(deque) and deque[0][const_num] == seq:
                deque.pop(0)

            while len(deque) > 0:
                t = deque[-1]
                add_pair(t[const_num], seq, pair)

                if deque[-1][const_val] < h[seq]:
                    deque.pop(-1)
                else:
                    for q in deque[::-1]:
                        add_pair(q[const_num], seq, pair)
                        if q[const_val] != h[seq]:
                            break
                    break

            deque.append((h[seq], seq))

        print result


if __name__ == '__main__':
    main()

备考

<题目来源: 京东2016实习生 原题连接-可在线提交(赛码网)>

问题描述

临近期末,让小东头疼的考试又即将到来了,并且是小东最不喜欢的科目。遗憾的是,小东得知d天后她必须参加这次考试。小东的父亲对她要求很是严格,要求她当即开始复习功课。为照顾她的情绪,父亲要求她天天该科目的学习时间在iminTime到imaxTime之间,并计划在考前检查小东是否按要求作了。若未能完成,小东将会受到惩罚。

如今小东的父亲要求检查小东的备考状况。遗憾的是,因为专一于备考,小东只是记录了本身备考的总时间sumTime,并无记录天天复习所用的时间,也不知道准备状况是否符合父亲的要求。她想知道是否可以制做一个知足要求的时间表以应付父亲的检查。

小东但愿你可以帮到她,你是否愿意?

图片描述

虽然都是4星题目,剩下两个题目相对就很简单了。而且剩下两个头目比较相似,咱们做为一类问题处理吧。
这个题目由于天天最有个最大和最小的限制。咱们先来考虑可行性,也就是只须要考察两个极端状况是否知足总的学习时间。
∑MinTimeDay[i] <= sumTime <= ∑MaxTimeDay[i] i∈[1..n]
知足后,根据题目要求尽量在前面的天数多学习,那么咱们从头开始处理,首先保证知足天天的最低时间,剩余的sumTime - ∑MinTimeDay[i]尽量的从一开始就多分配,但不要超过MaxTimeDay[i],用完后,其他的就按MinTimeDay[i]时间学习便可。

import sys


def main():
    day = []

    while True:
        line = map(int, sys.stdin.readline().strip().split())
        if len(line) < 2:
            return
        d, sum_time = line[0], line[1]
        t_min = 0
        t_max = 0

        for i in range(d):
            line = map(int, sys.stdin.readline().strip().split())
            day.append((line[0], line[1]))
            t_min += line[0]
            t_max += line[1]

        if t_min <= sum_time <= t_max:
            sum_time -= t_min
            print 'Yes'

            for d in day:
                if d[1] - d[0] <= sum_time:
                    print d[1],
                    sum_time -= d[1] - d[0]
                elif 0 < sum_time < d[1] - d[0]:
                    print d[0] + sum_time,
                    sum_time = 0
                else:
                    print d[0],
            print
        else:
            print 'No'

        day[:] = []


if __name__ == '__main__':
    main()

登山

<题目来源: 京东2017秋招 原题连接-可在线提交(赛码网)>

问题描述

小B曾经酷爱网络游戏,整日通宵达旦的玩游戏,致使身体素质急剧降低,所以下决心痛改前非,远离一切电子产品,并经过远足登山的方式改变生活方式并提升身体素质。因为担忧对身体形成太大的负荷,他老是选择最平坦的路径,并记录天天的行程状况及达到的最高海拔,使得连续两天之间的海拔之差最多为一个单位。不幸的是,在行程结束时,他不当心掉进河里,形成部分记录信息遗失。他想知道本身行程中可能达到的最高海拔,你是否可以帮忙?

图片描述

解决这个题目咱们须要按d做为key进行一个排序,按时间整理登山的高度后,考察两个横向的距离,也就是间隔的天数。所以题目规定天天只能上下最多一个单位高度,由于间隔的天数决定了咱们在纵向能够移动的高度。
首先检查第i个记录和i+1个记录的合理性。
abs(reci + 1 - reci) <= reci + 1 - reci
计算最大可达到的高度
h = (reci + 1 - reci + reci + 1 + reci) / 2
最后注意两个端点的处理便可。

import sys

const_d = 0
const_h = 1


def main():
    while True:
        rec = []
        line = map(int, sys.stdin.readline().strip().split())
        if len(line) < 2:
            return

        n, m = line[0], line[1]
        for i in range(m):
            line = map(int, sys.stdin.readline().strip().split())
            rec.append((line[0], line[1]))

        rec = sorted(rec)

        flag = True
        result = 0
        for i in range(m - 1):
            if abs(rec[i + 1][const_h] - rec[i][const_h]) > rec[i + 1][const_d] - rec[i][const_d]:
                flag = False
                print 'IMPOSSIBLE'
                break
            else:
                h = (rec[i + 1][const_d] - rec[i][const_d] + rec[i + 1][const_h] + rec[i][const_h]) / 2
                # print '---->', h
                hy_max = max(rec[i + 1][const_h], rec[i][const_h])
                result = max(result, max(hy_max, h))

        if flag:
            hy_board_max = max(rec[0][const_h] + rec[0][const_d] - 1, rec[-1][const_h] + n - rec[-1][const_d])
            result = max(hy_board_max, result)
            print result


if __name__ == '__main__':
    main()
相关文章
相关标签/搜索