编程珠玑之第二章

来源:互联网 发布:网络高等学历教育 编辑:程序博客网 时间:2024/05/16 01:54

1. 问题1

问题描述: 给定一个最多包含40亿个随机排列的32位整数的顺序文件,找出一个不在文件中的32位整数(在文件中至少缺失一个这样的数,为什么?).

(1)在具有足够内存的情况下, 如何解决该问题?

(2)如果有几个外部的"临时"文件可用,但是仅有几百字节的内存, 又该如何解决该问题?

问题思路: 由于2^32 = 4 294 967 296,超过给出的40亿,因此必定存在32位的整数不在此文件中.

(1) 当内存足够的情况下,使用位图法来寻找不在文件中的整数, 即位表示中,出现次数为0的整数.

此时需要的内存空间:(2^32)/8 = 最大数/一个字节可以表示8位 = 2^29B = 2^9MB = 512MB.

(2) 内存不够时,但可以使用外部临时文件, 使用二分查找.

       由于存储数据都是32位的, 可以根据32位中的某一位将原始数据分为两部分. 例如从最高位开始,统计最高位为1的数据个数,即大于等于2^31的元素,并将其存储在一个文件中;同时也统计最高位为0的数据个数,即小于2^31的数,并将其存储在另外一个文件中;

        接着比较最高位为1和最高位为0的个数大小, 若这两部分数的个数一样, 说明两边缺失数据个数是一样的, 由于只用找出一个缺失的个数,故随机选择一边进行下一步操作既可;

       若这两部分数据个数不一样, 说明个数少的缺失的数据个数肯定比个数多的那部分要多, 一个缺一个不缺或两个都缺, 此时肯定选择个数小的那部分进行下一步操作.

      最后,处理第二高位,递归进行,直到所有位都遍历结束,就可以找到缺失的值.

下面是java实现的代码,实际操作时应该从文件中读取数据,并将数据写入文件中:

<span style="font-size:14px;">package test;/* * 从随机排列的文件中,找出不在文件中的整数 * 重要思想是二分搜索 */public class FindLostNumber {public static void main(String[] args){int bits = 4; //数据是4位,用位操作比较简单,可以很简单明确的将数据分为大小一样的两部<span style="font-family:Arial;">分</span>int[]  arr = {0,1,2,3,4,5,6,7,9,10,11,13,14,15};int lostNumber = findLostNumber(arr,bits);System.out.print("the lost number is :"+lostNumber);}public static int findLostNumber(int[] arr,int bits){int num0 = 0, num1 = 0; //分别统计当前位是0和1的数的数目int len = arr.length;int[] arr0 = new int[len];int[] arr1 = new int[len];int lostNumber = 0;for(int i = bits-1;i>=0;i--){int flag = 1 << i;num0=0;num1=0;for(int j=0;j<len;j++){if((flag & arr[j]) == 0)arr0[num0++] = arr[j];elsearr1[num1++]=arr[j];}if(num0 < num1){arr = arr0;len = num0;}else{//代表缺失的数在当前位是1的这边,故缺失值的当前位为1arr = arr1;len = num1;lostNumber += flag;}}return lostNumber;}}</span>
2. 问题2

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

问题求解:

解决方案1: 将ab转换成ba, 首先对a求逆,得到a'b,然后只对b求逆,得到a'b',最后再对整体求逆,得到(a'b')',此时结果就是ba.即先局部翻转,然后再整体翻转.

相同解法的一个问题:输入一个英文句子,翻转句子中单词的顺序,但是单词内字符的顺序不变.

解法就是,首先将每个单词单独翻转,然后再整个句子翻转即可.

伪代码如下:

reverse(0,i-1); //cbadefgh

reverse(i,n-1);//cbahgfed

reverse(0,n-1);//defghabc

java实现代码如下:

<span style="font-size:14px;">package test;/* * 将一个n元向量向左移动i个位置 */public class Reverse {//实现在数组中从low到high元素的整体翻转public static void reverse(char[] arr,int low,int high){char temp;for(int i= 0;i<=(high-low)/2 ;i++){temp=arr[low+i];arr[low+i] = arr[high-i];arr[high-i] = temp;}}public static void main(String[] args) {// TODO Auto-generated method stubString str = "abcdefgh";int len= str.length();int i =3;char[] arr = str.toCharArray();reverse(arr,0,i-1);reverse(arr,i,len-1);reverse(arr,0,len-1);String str1="";for(int j=0;j<len;j++)str1+=arr[j];System.out.print(str1);}}</span>
解决方案2: 杂耍算法

思路: 对数组中的每一个元素向左移动i位,超过数组长度的可以取模回到数组中,最终就会得到结果.

步骤:(i表示循环移动的位数)

(1)先将x[0](即第一个存储的元素)保存在临时变量temp中;

(2)将x[i]移动到x[0]中,x[2i]移动到x[i]中,依次类推

(3)将x中的所有下标都对x.length取模,直到再次需要从x[0]中提取元素.然后此时从temp中提取元素,结束.

循环的终止条件: 当从循环的起始位置点提取元素时,此时循环终止.

注意: 当n和i互质时, 只需要一次循环就可以处理完所有元素; 当n与i不互质时,将原始数据分为一块块,每块大小为i,每次处理各个块的相同位置的元素.

java实现代码如下:

<span style="font-size:14px;">//求2个数的最大公约数i<npublic static int gcd(int n, int i){if(n<i){//确认n大于iint temp=i;i=n;n=temp;}if(n%i==0)return i;else{return gcd(i,n%i);}}//杂耍算法public static void rotate(char[] arr, int i, int len){int iter = gcd(len,i);for(int j=0;j<iter;j++){int firstNum = j;int next = firstNum + i;char temp = arr[firstNum];while(next != j){arr[firstNum] = arr[next];firstNum = next;next = (next + i)%len;}arr[firstNum] = temp;}}</span>
解决方案3: 块交换.

旋转向量其实就是交换向量ab,得到向量ba.a代表x的前i个元素.若a比b短, 则将b分成b1b2,且使得b2的长度和a一样,交换a和b2,得到b2b1a,则序列a此时处于最终位置,此时旋转向量的目标变为旋转b2b1;若a比b长,则将a分为a1a2,其中a1的长度和b的长度一样,交换a1和b,得到ba2a1,此时序列b到达最终位置,则原来的旋转向量目标变为旋转a2a1.递归上述过程,直到a和b的长度相等时,此时之需要交换a和b这两个序列即可.

java实现代码如下:

<span style="font-size:14px;">//采用块变换来实现左旋转,依次为数组,旋转开始位置,涉及旋转字符串长度,,旋转位数public static void rotate3(char[] arr, int start, int len, int bits){int leftstart = start;int rightstart = start + bits;int leftlen = bits;int rightlen = len-bits;if(leftlen < rightlen){swap(arr,leftstart,leftstart+len-bits,bits);rotate3(arr,leftstart,len-bits,leftlen);}else if(leftlen > rightlen){swap(arr,leftstart,rightstart,rightlen);rotate3(arr,leftstart+rightlen,len-rightlen,leftlen-rightlen);}else{swap(arr,leftstart,rightstart,rightlen);}}public static void swap(char[] arr, int l1, int r1,int len){char temp;for(int i =0;i<len;i++){temp = arr[l1+i];arr[l1+i] = arr[r1+i];arr[r1+i] = temp;}}</span>
3. 问题3

问题描述: 给定一个英语字典,找出其中所有的变位词集合.例如,'pots'/'stop'/'tops'互为变位词,因为没一个单词都可以通过改变其它单词中字母顺序得到.

问题求解: 原始问题可以转换为2个子问题:选择标识和集中具有相同标识的单词.

(1)为每一个单词生成一个标签, 且其所有的变位词都得有一样的标签: 一种方法: 把每个单词所包含的字母按照字母顺序排序, 在此基础上改进的方法是字母+出现次数,例如mississippi可以转换为i4m1p2s4,可以将出现一次的1省略.第二种方法: 使用一个包含26个整数的数组来标识每个字母出现的次数.

(2)根据标签收集单词,每个标签对应一个集合,这个集合包含其所有的变位词.可以使用hashmap来存储,键为标签,值为set,存储所有的变位词.

参考:

http://blog.csdn.net/insistgogo/article/details/7749328


0 0
原创粉丝点击