2. 啊哈,算法
A.给定一个包含32位整数的顺序文件,它至多包含40亿个这样的整数,并且整数的次序是随机的,请查找一个此文件中不存在的32位整数(至少必有一个遗漏,为什么?)。在有足够内存的情况下,你会如何解决这个问题?如果可以使用若干外部临时文件但可用主存却只有上百字节,你会如何解决这个问题?
由于文件包含了40亿个次序随机的32位整数,由于2^32个或4294967296个这样的整数,所以至少会出现一个遗漏。
如果有足够的主存,那么可以采用位图的方法来实现,将2^32个位表示对应的数字,依次读取文件中的数据,对其所对应的的位进行标识。再输入完所有数字之后,查找位表中的0,即可得知所缺失的数字。
B.请将一个具有n个元素的一维向量向左旋转i个位置,例如,假设n=8,i=3那么向量abcdefgh旋转之后得到向量defghabc。简单编码使用一个具有n个元素的中间向量分为n步完成次作业。你可以仅使用几十个自己的微小内存,花费与n成正比例的时间来旋转该向量吗?
解法1:
将x中的前i个元素复制到一个临时的数组中,接着将余下的n-i个元素左移i个位置,然后再将前i个元素从临时数组中复制到x中的后面i个位置上。这样可以实现,但是要使用i个额外的空间,空间开销太大。
解法2:
定义函数,它的作用是将x向左旋转一个位置(时间上与n成比例),然后调用该函数i次,即可实现i循环。但是总时间是 O(i*n),过于浪费时间。
解法3:
可以用一个堪称巧妙的杂技表演的方法,它的前提是要循环的i次必须是总数n的约数,否则如果他们只有约数1时,退化为解法2的复杂度。
先将x[0]移动临时变量t中,然后将x[i]移动到x[0],x[2i]移动到x[i]中,依次类推,直到结尾(可以使用下标对n取模的方法)。将t赋值到复制的最后一个元素中去。
t = A;
AB C D E F G HI J K L —>D B C GE F I H A J KL
依次循环,即可在正比于n的时间复杂度,以及t大小的空间下实现循环移位。
#include <stdio.h>
#include <stdlib.h>
int gcd(int i, int j)
{
while( i != j)
{
if(i > j)
{
i -= j;
}
else
{
j -= i;
}
}
return i;
}
#define Len 12
int main()
{
char str[Len];
int n = Len;
int rot = 3;
int k = 0, t = 0, j = 0, i = 0;
for(i = 0; i < Len; i++)
{
str[i] = 'a' + i;
}
for(i = 0; i < Len; i++)
{
printf("%c", str[i]);
}
printf("\n");
int temp = gcd(Len, 3);
for( i = 0; i < temp; i++)
{
t = str[i];
j = i;
do
{
k = j + rot;
if( k >= n)
{
k -= rot;
break;
}
str[j] = str[k];
j = k;
}while( 1);
str[j] = t;
}
for(i = 0; i < Len; i++)
{
printf("%c", str[i]);
}
printf("\n");
return 0;
}
解法4:
不同的算法源自对问题的不同看法:旋转向量x实际上就是将向量ab的两个部分交换为ba,这里a代表x的前i个元素。假设a比b短,将b分割为和两部分,其中的长度和a的长度一样,交换a和,将a换成a,下面的工作就是将交换过来即可,由于这个问题与原问题相同,因此可以采用递归的方式进行解决。
#include <stdio.h>
#include <stdlib.h>
#define Len 19
void reverse_rot(char str[], int len, int rot)
{
int remain = len - rot;
int i = 0;
char tmp;
if(rot == 0)
{
return ;
}
if( remain > rot)
{
for( i = 0; i < rot; i++)
{
tmp = str[i];
str[i] = str[len - rot + i];
str[len - rot + i] = tmp;
}
reverse_rot(str, len - rot, rot);
}
else if( remain < rot)
{
for( i = 0; i < remain; i++)
{
tmp = str[i];
str[i] = str[len - remain + i];
str[len - remain + i] = tmp;
}
reverse_rot(str + remain, len - remain, rot - remain);
}
else
{
for( i = 0; i < remain; i++)
{
tmp = str[i];
str[i] = str[len - rot + i];
str[len - rot + i] = tmp;
}
return ;
}
}
int main()
{
char str[Len];
int n = Len;
int rot = 3;
int k = 0, t = 0, j = 0, i = 0;
for(i = 0; i < Len; i++)
{
str[i] = 'a' + i;
}
for(i = 0; i < Len; i++)
{
printf("%c", str[i]);
}
printf("\n");
reverse_rot(str, Len, rot);
for(i = 0; i < Len; i++)
{
printf("%c", str[i]);
}
printf("\n");
return 0;
}
解法5:
我们将问题看为把数组ab转换成为数组ba,但是也假定我们具有在数组中转置指定部分元素的函数,首先从ab开始,转置a得到a’b,再转置b得到a’b’,最后将整个数组再转置得到(a’b’)’ = ba。
reverse(0, i-1);
reverse(i, n-1);
reverse(0, n-1);
#include <iostream>
using namespace std;
void reverse(char str[], int s, int d)
{
char c;
if( s >= d)
{
return ;
}
for(int i = 0; i < (d - s + 1)/2; i++)
{
c = str[s + i];
str[s + i] = str[d - i];
str[d - i] = c;
}
}
int main()
{
int rot = 3;
int n = 10;
char str[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
for(int i = 0; i < n; i++)
{
cout << str[i];
}
cout << endl;
reverse(str, 0, rot - 1);
reverse(str, rot, n - 1);
reverse(str, 0, n - 1);
for(int i = 0; i < n; i++)
{
cout << str[i];
}
cout << endl;
return 0;
}
C.给定一本英语单词词典,请找出所有的变位词集。例如,因为“pots”,“stop”,“tops”相互之间都是另一个词的各个字母改变顺序而构成的,因此这些词相互之间就是变位词。
将词典中的每个单词进行“签名”,这样同一个变位词类中的单词具有相同的“签名”,然后将具有相同千米昂的单词归到一起,这样原来的变位词问题转换为两个问题:计算一个单词的签名;收集具有相同签名的单词。
补充(习题):
1.给定一个包含了4300000000个32位整数的顺序文件,请问如何可以找到一个至少出现了两次的整数?
内存充足:
1. 使用位图的方法,一旦内存中对应位重复置位,则说明本数字是至少出现了两次的整数。
2. 或将顺序文件进行排序,再进行顺序查找即可得到至少出现两次的数字。
内存不足:
使用二分查找的方法,从表示每个整数的32位的角度来考虑二分搜索,按照每一位0/1区别进行区分,将所有数字依次分到两个临时文件中,数量多的一个文件就是出现重复数字的一组,继续对它进行二分查找。
这种方法有一个不足:在最坏情况下,所有的数字都相同,那么它的复杂度为O(nlogn),复杂度过大。

611

被折叠的 条评论
为什么被折叠?



