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]; else arr1[num1++]=arr[j]; } if(num0 < num1){ arr = arr0; len = num0; }else{//代表缺失的数在当前位是1的这边,故缺失值的当前位为1 arr = 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 stub String 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<n public static int gcd(int n, int i){ if(n<i){//确认n大于i int 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