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