2015年5月微软的一道面试题:
来源:同学叙述
题目:
用0,2,4,6表示0-9任意的数字组成的序列。
分析:
乍一看,就是编码部分的知识。我们知道一位中有4个有效信息位:分别是0,2,4,6,它们要表示一个10位的信息:0-9。
(*在这里,肯定有很多人想到了哈夫曼编码什么的,我觉得这里应该是未知0-9这十个数的概率分布的,所以用哈夫曼编码并不合适。
当然了,在面试中你要提前先问清楚,作为你思考的一个过程。)
最先想到的可能就是用两个四位信息位表示一位10位信息位,4^2 = 16 > 10,可以满足。平均编码长度为2位。
对应关系为
编码 | 对应的数字 |
---|---|
00 | 0 |
02 | 1 |
04 | 2 |
06 | 3 |
20 | 4 |
22 | 5 |
24 | 6 |
26 | 7 |
40 | 8 |
42 | 9 |
44 | 保留 |
46 | 保留 |
60 | 保留 |
62 | 保留 |
64 | 保留 |
66 | 保留 |
接着我们进行优化,考虑的就是减少编码长度,因为考虑到16位中是有浪费的,所以比较容易得会想到用3*3+1的方式:
即所谓的把0,2,4三个数字当作一个信息位,那么用两位就可以表示出9位信息位,然后再用6表示余下一位。
这样的方式有如下对应关系:
编码 | 对应的数字 |
---|---|
00 | 0 |
02 | 1 |
04 | 2 |
20 | 3 |
22 | 4 |
24 | 5 |
40 | 6 |
42 | 7 |
44 | 8 |
6 | 9 |
(*写到这里,也许有人可能想到上面第一种方法中也可以进行优化,即用44-66的6位表示其他两位数字,比如用44表示00,这样就可以在一定程度上减少编码长度)
再思考:
但是上面的第二种方法并不是最优,或者说不是思考的尽头。
我们不妨假设0-9出现的概率相同,那么6对应的9的编码长度是1,其实只是减少了所有字符中1/10的长度,可以看到减少的长度很有限,因为另外有9/10的编码长度还是2的。
所以并不是最优的,我们应该朝怎么样把整体的编码长度有效降低。
这里,就有信息有效位的概念(我自己瞎想的)
对于0,2,4,6,其有效位是4,那么如果有n位就能表示4^n种的信息状态,而对于0-9,需要表示的是10种信息,如果有m位,则有10^m。
前提:需要表示的信息概率分布相同(也就是出现的概率差不多)时
要想编码长度最短,其实就是找到n和m,使4 ^ n >= 10 ^ m,并且使其中的(4 ^ n - 10 ^ m) / 10 ^ m尽量小,也就是浪费掉的或者说保留掉的位占得比率尽可能小。
我们可以找到,当n = 5时,m = 3时,可以满足上述条件,刚好是4 ^ 5 = 2 ^ 10 = 1024 > 1000 。
因此,我们也就相当于用5位表示了3位,平均编码长度为5 / 3 = 1.67位。
*这里的1.67是在0-9任意的数字组成的序列比较长的情况下的,而不是针对它们序列很短的时候,后者肯定是短编码比较好。
编码 | 对应的数字 |
---|---|
00000 | 000 |
00002 | 001 |
00004 | 002 |
... | ... |
66426 | 999 |
66440 | 保留 |
... | ... |
66666 | 保留 |
但是呢,这里会出现一个问题,就是当我们用5位表示3位的时候,如果遇到序列并不是3的倍数的怎么办,我们如何来表示末尾的比如“3空空”或“34空”之类的数字呢。
这里我们可以用保留的位数做文章。
比如令66440表示末尾刚好是不用截取的,即刚好是3的倍数的。
令66442表示截取1位的,即刚好是3倍数多1位的。
令66444表示截取2位的,即刚好是3倍数多2位的。
那么对于9990的话,我们就可以编码:66426 00000 66442来进行编码。
所以,是可以完美解决5位编码3位的问题的,平均编码长度为1.67,应该是最短的。
再思考:
不过,这里还有一个小问题:虽然编码长度短了,但是,对于编码和接码过程中需要维护的映射关系却长了,比如第一第二种方法都只用维护10个映射关系即20空间大小,而对于第三种方法却需要维护1000各映射关系即2000个空间大小。这对于内存和处理来说会比较麻烦,但是至少在传输过程中会比较轻便。
这也和现实生活中很像,你要想路上轻便点,就需要多花点时间打包整理好行李。
——Apie陈小旭