这段时间工做上的事情特别忙,因此也有一段时间没有更新了,此次咱们来处理今日头条2017秋招的题目, 共4个题目,整体来讲要100%经过测试数据有必定难度。此次咱们选择其中的3个问题来进行简单分析,期间会提到KMP算法的next数组和Trie树在此次解题中的应用。算法
下次更新计划详细介绍Trie树以及应用,并解析剩余的一个题目,这个题目依然会用到Trie树来处理。网上关于题目的解析也比较多,算法也比较多,我会尝试用一些比较高效而简洁算法来实现。在赛码网题目都没有给出数据规模,我在题目后进行了补充,由于数据规模对咱们选择算法很重要。数组
<题目来源: 今日头条2017秋招 原题连接-可在线提交(赛码网)>测试
给定整数n和m,将1到n的这n个整数按字典序排列以后,求其中的第m个数字。 对于n = 11,m = 4,按字典序排列依次为1, 10,
11, 2, 3, 4, 5, 6, 7, 8, 9,所以第4个数字为2。
数据规模
对于20%的数据, 1 <= m <= n <= 5
对于80%的数据, 1 <= m <= n <= 10^7
对于100%的数据, 1 <= m <= n <= 10^18
咱们不妨先列出一些数字来观察字典序究竟是个什么序:例如1 - 103的字典序的前面若干个数
1
10
100
101
102
103
11
12
13
14
15
16
17
18
19
20
21
...spa
比较典型的一个树形结构,既然是字典序,咱们天然联想到字典树,也就是Trie树了。观察下构造的对应的Trie树部分结构,以下图
*关于Trie树的基础知识和更多应用,我计划在下次详细介绍。code
这个Trie能够看作是一颗十叉树,若是要获取完整的字典序,咱们仅须要按深度优先的顺序去遍历trie的每一个节点,对于每一个节点,按0-9的顺序访问叶节点便可。所以,若是我要们打印1-n的字典序就能够用深度优先搜索(dfs)来实现了。对于这个题目而言,咱们只须要知道第m个数是什么,至于以前的m-1个数是什么咱们其实并不关心。排序
因为从1开始,咱们先统计以1开头的子树的节点总数k(注意须要知足全部包含在该子树的数 <=n):
a.若是k > m m,咱们要找的第m个数就在这个节点开头的子树里面,换言之,第m个数必定是这个节点表示的数开头的,咱们令m = m - 1,而后继续查找下一层肯定下一位数字
b.若是k <= m ,那么不在这个节点的子树中,咱们继续在这个节点的右侧(兄弟节点中),因为以前节点和其子树包含了若干个节点t,咱们令m = m - t,而后继续查找同一层肯定下一位数字
c.当m = 0时,咱们就获得想要的第m个数图片
最后就只剩下一个须要解决的问题:如何计算以一个特定数开头的全部知足 <= n的全部数的总个数,好比能够是‘1’开头,‘23’开头等等ip
观察Trie树的结构,考虑开头为prefix的数所包含的全部可能的数字,咱们设置两个区间标识变量begin和end,在prefix的最后一层trie树的节点中,例如prefix=234,那么最后一层节点为4,对于3位的状况,最大的数为end = begin - 1,若是end比n小,那么说明以prefix开头的数位数比3位更多,考虑下一位,一样,下一位开头为begin = begin 10, end = end 10(除第一层外,每层的数的个数都是上层的10倍),继续比较end和n的关小,看是否须要继续向下寻找,若是end 已经小于等于n了,那么当前这层的节点数就是n + 1 - beginci
至此,该问题得以解决。固然,受限于题目的数据规模,若是是按DFS的方法去逐位构造统计,只能经过30%的数据。字符串
import sys def get_subtree_num(prefix, n): count = 0 begin = prefix end = begin + 1 while begin <= n: count += min(n + 1, end) - begin begin *= 10 end *= 10 return count if __name__ == '__main__': line = map(int, sys.stdin.readline().strip().split()) n, m = line[0], line[1] subtree_cnt = 0 r = 1 m -= 1 while m: subtree_cnt = get_subtree_num(r, n) if subtree_cnt <= m: m -= subtree_cnt r += 1 else: r *= 10 m -= 1 print r
<题目来源: 今日头条2017秋招 原题连接-可在线提交(赛码网)>
咱们规定对一个字符串的shift操做以下: shift(“ABCD”, 0) = “ABCD” shift(“ABCD”, 1) =
“BCDA” shift(“ABCD”, 2) = “CDAB” 换言之, 咱们把最左侧的N个字符剪切下来, 按序附加到了右侧。
给定一个长度为n的字符串,咱们规定最多能够进行n次向左的循环shift操做。若是shift(string, x) = string (0<= x <n), 咱们称其为一次匹配(match)。求在shift过程当中出现匹配的次数。
数据规模
30%的样例中输入字符串的长度<100
100%的样例中输入字符串的长度<10^6
这个题目能够这样来理解,每次从串的末尾拿走一个,放到串的开头,而后比较和原串是否相同,而后再执行一样的操做,直到串中的每一个字符都被移动了一次为止;
简单来讲,暴力解法仍然是首选,咱们能够彻底模拟题目中的shifting操做,而后进行字符串比较统计结果便可。可是,这样作的效率是O(n^2),对于70%的数据规模在10^6,是不可能在限定时间内出解的。
在上面的暴力解法中,咱们仅移动了一位,就又要进行一次长度为n的比较,是否代价过大。再回到题目自己,如何出现相同的状况?
设s是原串,长度为n,咱们从s左侧顺序拿走t个字符组成串s1,此时,咱们将该串拼接到s后面,表示成s[t]s[t + 1]s[t + 2]...s[n - 1]s[n]s[n + 1]...s[n + t - 1]
这个串须要和原串s[0]s[1]...s[n - 1]相等,也就是s[t]st + 1 ... = s[0]s[1]s[2]...[t - 1],要出现这样的状况,只有当整个串是按s[0]s[1]s[2]..s[t - 1]做为一个循环节出现的形式,例如
abcabcabc, abcaabca, aaabbb
至此,这个问题咱们转换为一个查找循环节的问题
咱们能够先枚举循环节的长度,而后检查是否知足,检查的方法就是字符串匹配了,尽管咱们能够采用一些高效的匹配办法,但这个仍然不是最佳的解法,其实查找循环节有个很是经典的算法是利用kmp算法中的next数组,关于KMP算法网上随便一搜索就是一大堆,其中有些写的很好,也很容易理解,这里我只补充两张图来帮助理解next数组的含义,整个kmp算法的核心就在于这个next数组了。
最后,我看了下其余的一些解法,发现C++竟然能够用substr过掉,还有用hash的,都是一些比较好的方法。若是咱们使用相对暴力的一些算法,咱们也要尽量的减小计算,好比,咱们枚举的循环节长度为k,若是len(s) % k != 0,显然就不须要再进行比较,另外循环节的长度不是超过len(n)/2。
import sys def get_next(s): next = [0 for i in range(len(s) + 1)] j = next[0] = -1 i = 0 while i < len(s): if j == -1 or s[i] == s[j]: i += 1 j += 1 next[i] = j else: j = next[j] return next[-1] def main(): line = map(str, sys.stdin.readline().strip().split())[0] c = get_next(line) print len(line) / (len(line) - c) if len(line) % (len(line) - c) == 0 else 1 if __name__ == '__main__': main()
<题目来源: 今日头条2017秋招 原题连接-可在线提交(赛码网)>
头条的2017校招开始了!为了此次校招,咱们组织了一个规模宏大的出题团队。每一个出题人都出了一些有趣的题目,而咱们如今想把这些题目组合成若干场考试出来。在选题以前,咱们对题目进行了盲审,并定出了每道题的难度系数。一场考试包含3道开放性题目,假设他们的难度从小到大分别为a,
b, c,咱们但愿这3道题能知足下列条件:
a<= b<= c, b - a<= 10, c - b<= 10
全部出题人一共出了n道开放性题目。如今咱们想把这n道题分布到若干场考试中(1场或多场,每道题都必须使用且只能用一次),然而因为上述条件的限制,可能有一些考试无法凑够3道题,所以出题人就须要多出一些适当难度的题目来让每场考试都达到要求。然而咱们出题已经出得很累了,你能计算出咱们最少还须要再出几道题吗?
这个题目应该是4个题目中最简单的一个题目了。根据题目条件n个题目要分到若干考场,且每一个考场都要包含3个题目,显然题目个数须要是3的倍数。
根据题目中的a<= b<= c, b - a<= 10, c - b<= 10,其实说每3个题目之间难度递增,且在此条件下,两两之间的难度差不超过10。
因此考虑先对全部题目按难度从小到大排序,这样能确保找出尽量多的题目知足上述条件,若是不知足就必须增长题目。
思考:如何证实排序的状况下增长的题目不会比乱序的状况更多?
排序后,咱们只须要按顺序观察题目是否知足该条件,若是不知足状况,咱们就不得不增长题目了。
从右侧开始处理,设置当前的状态0 - 2,分别表示添加第1 - 3个题目;
1.状态为0时,首先添加第一个题目,状态设置为1;
2.状态为1时,已经添加了第一个题目,那么检查下个题目和已经添加题目的难度差,若是<=10,状态设置为2;若是<=20,那么在这两个题目中加一个题目便可,状态设置为回到0;
3.状态为2时,已经添加了前两个题目,检查第下个题目是否知足<=10,若是不知足,增长一个题目,将下个题目做为下次添加的第一个题目,最终状态都回到0;
注意最终的题目数须要为3的倍数
最后,这个题目的测试数据应该存在问题,提交的代码中有几个错误算法竟然accept了,好比数据
2
3 17
应该是增长一个题目,而不是4个
import sys def main(): n = map(int, sys.stdin.readline().strip().split())[0] array = map(int, sys.stdin.readline().strip().split()) array.sort() sta = 0 r = 0 p = 0 while p < len(array): if sta == 0: a = array[p] p += 1 sta = 1 elif sta == 1: b = array[p] if b - a <= 10: p += 1 sta = 2 elif b - a <= 20: r += 1 p += 1 sta = 0 else: r += 2 sta = 0 else: c = array[p] if c - b > 10: r += 1 else: p += 1 sta = 0 # print 'sta = ', sta if sta: r += 3 - sta print r if __name__ == '__main__': main()