编程珠玑第2章 习题解答

A、给定一个最多包含40亿个随机排列的32为整数的顺序文件,找出一个不存在文件中的32位整数(在文件中至少缺失一个这样的数——为什么?)。在具有足够内存的情况下,如何解决该问题?如何有几个外部的“临时”文件可用,但是仅有几百字节的内存,又该如何解决问题?


1.在文件中至少缺失一个这样的数?为什么呢?这是因为32为表示能表示N=232-1个数,(42 9496 7296)这个数>4*10的九次方=40亿大的数,因此肯定在文件中缺失一个这样的数。

2.在具有足够内存的情况下如何解决这个问题?根据前一篇介绍的位向量表示法,可以判断该数是否存在。不过要一次性将所有数都映射到内存中,大约所需要40 0000 0000/8Byte=500MB的内存。

3.‍‍0-1二分查找:探测每个整数的每个bit是0还是1,读取n=40亿个整数,第1个bit为0或为1的放到不同的文件中(每个至多为n/2亿),少于N/2个数的那组 必定缺少某个数,接着探测第2个bit是0还是1,输入至多n/2亿,输出至多n/4亿,少于N/4个数的那组 必定缺少某个数,以此类推,总的运行时间和n成正比。通过对某组排序扫描可以得到缺失的数,这样运行时间变为o(nlogn)。


Q2:给定一个包含4300000000个32位整数的顺序文件,请问如何找到一个至少出现两次的整数?

解答:二分查找。由于4.3G>32位的整数空间,根据鸽笼原理,肯定会有重复的整数。搜索范围从所有的32位正整数开始(全部当成unsigned int,简化问题),即[0, 2^32),中间值即为2^31。然后遍历文件,如果小于2^31的整数个数大于N/2=2^31,则调整搜索范围为[0, 2^31],反之亦然;然后再对整个文件再遍历一遍,直到得到最后的结果。这样一共会有logn次的搜索,每次过n个整数(每次都是完全遍历),总体的复杂度为o(nlogn)。

例子:数组[4,2,5,1,3,6,3,7,0,7],假定从3位的整数空间内搜索。第一次的范围为[0,8),遍历过后发现[0,4)范围内的整数个数为5,于是调整为搜索[0,4)范围内的整数。第二次发现[2, 4)范围内的证书为3,大于2,于是调整为[2, 4)。再经过第三次的遍历,找出3为重复出现的整数。

改进:上面的办法有很多的冗余。于是提出了一个办法:建立一个新的文件(是顺序文件就可以)。在一次遍历过后,确定搜索的范围后,把原有文件里这个范围内的整数写到新的文件里去,下次搜索就只要搜索这个新文件了。这样可以得到近似线性的复杂度(但是常数项应该很大)。


B.将一个n元一维向量X向左旋转i=rotdist个位置。例如,当n=8且i=3时,向量abcdefgh旋转为defghabc。简单的代码使用一个n元的中间向量在n步内完成该工作。你能否仅适用数十位额外字节的存储空间,在正比于n的时间内完成向量的旋转?

方法一:额外的空间,将X中的前i个元素复制到一个临时数组中,接着将余下n-i个元素左移i个位置,然后再将前i个元素从临时数组中复制回X中后面的位置,缺点:i个额外的空间。

方法二:定义一个将X中元素左移一位的函数,然后调用该函数i次,缺点:太费时间

方法三:杂技法。该方法很巧妙,但不太容易理解,不知道算法的作者是怎么想出来的。
方法四:分块求逆。这个算法很好用,通过分别求逆后再求逆来得到最终的结果。
比如要将n元向量旋转i位,过程为:
reverse(0,i-1)
reverse(i,n-1)
reverse(0,n-1)
我的思路:可以用空间换时间的思想来解决。比如要旋转的n元向量为abcdefgh,要将它旋转3位:defghabc我们可以开辟一块新空间来再存储一份n元向量,将它们拼接,为abcdefghabcdefgh,则所要求的旋转结果为这个字符串的子串,取s[3:len(n)+3]即为所求。

方法五:Gries的迭代解决方法,块交换:假设a比b短,将b分为b1和br(和a一样长) 两部分,a和br交换,即ab1br交换为brb1a,递归交换brb1.不过这个每个元素不能一步到位。不如方法四高效。


C.给定一本英语单词词典,请找出所有的变位词集。例如,因为“pots”,“stop”,“tops”相互之间都是由另一个词的各个字母改变序列而构成的,因此这些词相互之间就是变位词。

错误1:考虑如何求解某个单词的各个字母的所有置换,n!个可能。

错误2:比较所有的单词对

正确方法:给每个单词签名,同一变位词就具有相同的签名,然后将相同签名的单词归拢在一起。考虑两个子问题:签名(签名按字母顺序排序单词中的字母)+收集相同签名的词(按签名排序)。

解决思路就是对每一个单词进行标记,使得互为变位词的单词有相同的标记,而不是变位词的单词有不同的标记。

这里列出几种可用的标记方法:
(1)对每个单词进行字典序排序,则互为变位词的单词有着相同的排序(若单词量大,对每个单词排序比较耗时)
(2)将26个英文字母分别对应一个素数,则一个单词的标记为组成它的每个字母的素数乘积。这样保证了变位词的标记时一样的,而非变位词有不同      的标记。但当一个单词长度比较长时,计算一个乘积结果,数据量也比较大。
(3)统计一个单词中各字母出现的次数,并以此作为标记索引。如mississippi对应i4m1p2s4,可以将1省略,为i4mp2s4.

第二章的思考题总结:

1. 考虑查找给定输入单词的所有变位词的问题。仅给定单词和字典的情况下,如何解决问题?如果有一些时间和空间可以在响应任何查询之前预先处理字典,又会怎样?

答:没有时间进行预处理,则直接按该单词的标识值去查字典,标识相同的单词输出。

如有时间和空间进行预处理,可以先对整个字典进行标识计算,将计算值作为索引hash到不同的数组中,并将标识相同的单词(变位词)链在同一个链表中。查找时按给定的单词去查索引表(第一个节点即为链表表头节点),然后一次取出变位词输出。


第二题在上面已经解答。


7.将4000*4000的矩阵转置。

书中给出的解决方案是对每个元素插入行号,列号,根据转置矩阵的特点,对原来的矩阵先按列排序,再按行排序,得到结果矩阵。


8.给定一个n元实数集合,一个实数t和一个整数k,如何快速确定是否存在一个k元子集,其元素之和不超过t?

第一感觉想到的是排序,然后看前k个数的和是否不超过t,不超过的话肯定存在。更优的方法用O(N)的选择算法求出第k大的数,然后把数组扫描一遍,求出小于第k大数的数的和sum,加上第k大。这样看似没有什么错误,但是仔细想想,如果第k-1大,第k大,第k+1大的数一样,肿么办?。。。。。。。。 easy~扫描的时候顺便统计小于第k大数的数的个数a,和第k大的数的个数b,嗯,然后如果a<k-1,就从b中取出m个,直到a+m == k-1。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值