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为重复出现的整数。
改进:上面的办法有很多的冗余。于是提出了一个办法:建立一个新的文件(是顺序文件就可以)。在一次遍历过后,确定搜索的范围后,把原有文件里这个范围内的整数写到新的文件里去,下次搜索就只要搜索这个新文件了。这样可以得到近似线性的复杂度(但是常数项应该很大)。
若整数是排好序的,一个线性查找的解法
public static void main(String[] args)
{
int[] arr = {2,3,4,5,7,11,12,12,13,14,15};
int i=0;
int increase=arr[0]; //suppose = arr[0];
for(;i<arr.length;i++)
{ //suppose++;
if(arr[i] > i + increase) //if(arr[i] > suppose)
{
increase += (arr[i]-i-increase); //suppose += arr[i];
continue;
}
if(arr[i] < i+increase) //if(arr[i] < suppose)
{
System.out.println("重复的数字是:"+arr[i]);
break;
}
}
}
B.将一个n元一维向量X向左旋转i=rotdist个位置。例如,当n=8且i=3时,向量abcdefgh旋转为defghabc。简单的代码使用一个n元的中间向量在n步内完成该工作。你能否仅适用数十位额外字节的存储空间,在正比于n的时间内完成向量的旋转?
方法一:额外的空间,将X中的前i个元素复制到一个临时数组中,接着将余下n-i个元素左移i个位置,然后再将前i个元素从临时数组中复制回X中后面的位置,缺点:i个额外的空间。
方法二:定义一个将X中元素左移一位的函数,然后调用该函数i次,缺点:太费时间
方法三:先将X[0]移动到临时空间t中,然后将X[i]移动到X0中,X2i移动到Xi,下标对n取模依次类推,知道我们有回到X0中提取数据(改从t中取)。如果该过程不能移动所有元素,我们再从X1中开始移动(循环何时终止?)
i=rotdist 和 n的最大公约数 是循环的次数:循环替代元素,每一步前进rotdist=i长度,相当于把所有元素分成了gcd(n,rotdist)个组,每次只有组内的位置可以移动,类此CUDA里面放置bank-conflict也要gcd(i,j) = 1,这样多个i就不会重复访问一个j。
http://hi.baidu.com/banyantree/blog/item/2563b5cc9eb00d1700e9284b.html
运行swap(x, 10, 2),观察每一趟的输出。这个需要2趟。
j:0, k:2
2, 1, 2, 3, 4, 5, 6, 7, 8, 9
j:2, k:4
2, 1, 4, 3, 4, 5, 6, 7, 8, 9
j:4, k:6
2, 1, 4, 3, 6, 5, 6, 7, 8, 9
j:6, k:8
2, 1, 4, 3, 6, 5, 8, 7, 8, 9
2, 1, 4, 3, 6, 5, 8, 7, 0, 9
*****************
j:1, k:3
2, 3, 4, 3, 6, 5, 8, 7, 0, 9
j:3, k:5
2, 3, 4, 5, 6, 5, 8, 7, 0, 9
j:5, k:7
2, 3, 4, 5, 6, 7, 8, 7, 0, 9
j:7, k:9
2, 3, 4, 5, 6, 7, 8, 9, 0, 9
2, 3, 4, 5, 6, 7, 8, 9, 0, 1
*****************
这个方法的好处是每一个元素一步到位,大大减少了交换次数。
//求最大公约数
int gcd(int i, int j){ i > j
while(i != j){ while(i%j){
if(i > j) i =j;
i -= j; j = i%j;
else }
j -= i; return j;
}
return i;
}
void bitswap(int *x, int n, int rotdist){
int i, t, j, k, ii;
if(rotdist == 0 || rotdist == n)
return;
for(i = 0; i < gcd(rotdist, n); i++){
t = x[i];
j = i;
while(1){
k = j + rotdist;
if(k >= n)
k -= n;
if(k == i)
break;
x[j] = x[k];
j = k;
}
x[j] = t;
}
}
方法四:方向交换,手摇法原地递归旋转向量或字符串。这种算法省内存,但比直接开一块内存然后交换的方法要慢。
void reverse(char* s, int start, int end){
int i, j, tmp;
for(i = start, j = end; i < j; i++, j--){
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
int main(int argc, char* argv[]){
int len = strlen(argv[1]);
int i = atoi(argv[2]);
reverse(argv[1], 0, i - 1);//转A得 AB --> ArB
reverse(argv[1], i, len - 1);//转B ArB-->ArBr
reverse(argv[1], 0, len - 1);//转ArBr)r 得 BA. ArBr --> BA
printf("%s\n", argv[1]);
}
方法五:Gries的迭代解决方法,块交换:假设a比b短,将b分为b1和br(和a一样长) 两部分,a和br交换,即ab1br交换为brb1a,递归交换brb1.不过这个每个元素不能一步到位,不如上一个效率高。
//x[a...a+m-1]和x[b...b+m-1]交换
void swap(int *x, int a, int b, int m){
int tmp;
int i;
for(i = 0; i < m; i ++){
tmp = x[a + i];
x[a + i] = x[b + i];
x[b + i] = tmp;
}
}
void swapm(int* x, int n, int rotdist){
int i, j, p, ii;
if(rotdist == 0 || rotdist == n)
return;
i = p = rotdist;
j = n - p;
while(i != j){
if(i > j){
swap(x, p - i, p, j);
i -= j;
}else{
swap(x, p - i, p + j - i, i);
j -= i;
}
}
swap(x, p - i, p, i);
}
C.给定一本英语单词词典,请找出所有的变位词集。例如,因为“pots”,“stop”,“tops”相互之间都是由另一个词的各个字母改变序列而构成的,因此这些词相互之间就是变位词。
错误1:考虑如何求解某个单词的各个字母的所有置换,n!个可能。
错误2:比较所有的单词对
正确方法:给每个单词签名,同一变位词就具有相同的签名,然后将相同签名的单词归拢在一起。考虑两个子问题:签名(签名按字母顺序排序单词中的字母)+收集相同签名的词(按签名排序)。
//签名sign
int charcmp(char *a,char *b)
{
return *a - *b;
}
#define WORDMAX 100
int main(void)
{ char word[WORDMAX],sign[WORDMAX];
while(scanf(%s,word) != EOF)
{ strcpy(sign,word);
qsort(sign,strlen(sign),sizeof(char),charcmp);
printf("%s %s\n",word,sign);
}
return 0;
}
//输出相同签名的 变位词 到单行squash
int main()
{ char word[WORDMAX],sign[WORDMAX],oldsign[WORDMAX];
int linenum = 0;
while(scanf("%s %s",word,sign) != EOF)
{ oldsign = sign;
if(strcmp(sign,oldsign) != 0 && linenum > 0)
printf("\n");
strcpy(oldsign,sign);
linenum++;
printf("%s ",word);
}
printf("\n");
return 0;
}
sign < dictionary | sort | squash > gramlist
Q7:为了转置以行为主的矩阵,预先考虑每条记录的列和行,然后调用系统磁带排序按列排序,再按行排序,然后使用另一个程序删除行列号。
Q8:定一个具有n个元素的实数集,一个实数t以及一个整数k,请问如何快速确定该实数集是否存在两个元素,它们的总和为t?
寻找k次第k小?
本文出自:http://hi.baidu.com/renzherl/item/25c9504eef7062efa4c066a4