《编程珠玑》第二章
一、题目:
A题:给定一个最多包含40亿个随机排列的32位整数的顺序文件,找出一个不在文件中的32位整数。在文件中至少存在这样一个数?
1、如果有足够的内存,如何处理?
2、如果内存不足,仅可以用文件来进行处理,如何处理?
答:
1 采用位图,表示所有的32位整数,约要512M空间
2 内存不足,可以采用如下思想:
按最高位分为两段,没有出现的那个数,肯定在比较小的段里面。
如果比较少的段最高位为1,那么缺少的那个数的最高位也为1.
如果比较少的段最高位为0,那么少的那个数的最高位也是0.
依次按以上方法去处理每个位。
B题:字符串循环移位比如abcdef 左移三位,则变成defabc
答:
解法一:旋转前i个字符,可以预先将这i个字符拷贝到辅助空间,剩下的n-i向前移动i个位置,填充剩余的i个空间。
解法二:用递归函数每次向前移位一个,递归i次。
解法三:
#include<stdlib.h> #include<stdio.h> #include<string.h> static void _rever(char *strseq,int n) { int i = 0,j = n-1; char t; while(i < j) { t = strseq[i]; strseq[i] = strseq[j];strseq[j] = t; i++;j--; } } char* rever(char *strseq,int i,int len) { if(strseq == NULL && i > len ) return NULL; if(i <= 0 || i == len) return strseq; _rever(strseq,len); _rever(strseq,i); _rever(strseq+i,len-i); return strseq; } void main() { char strseq[100]; scanf("%s",strseq); char *result = NULL; printf("%d \n",strlen(strseq)); result = rever(strseq,3,strlen(strseq)); if(result) printf("%s \n",result); system("pause"); }
当i > n时,我们还可以的处理方式是 i = i % n;
解法四:
#include <iostream> using namespace std; static void swap(char &a,char &b) { char t = a; a = b; b = t; } void doRotate(string &str,int n,int m,int start,int end,bool flag) { if(m == 0 || start == end) return; //标记为true,表示向右旋转前m个字符 if(flag == true) { int i = start; int j = start + m; int step = n - (n%m + m); int k = 0; for( ;k < step;k++,i++,j++) swap(str[i],str[j]); doRotate(str,n-k,n%m,i,end,false); }else { int i = end; int j = end - m; int step = n - (n%m + m); int k = 0; for(;k < step;k++,i--,j--) swap(str[i],str[j]); doRotate(str,n-k,n%m,start,i,true); } }; void main() { string str = "abc12345"; cout<<str<<endl; doRotate(str,str.length(),3,0,str.length()-1,true); cout<<str<<endl; system("pause"); }
C题:给定一个单词集合,找出可以相互转换的集合。比如abc bca cba都可以相互转换。(变位词)
答:使每个变位词类有一个唯一的标识,然后对标识进行排序,即集中具有相同标识的单词。
/*C++实现*/ #include <iostream> #include <string> #include <vector> #include <map> #include<algorithm> using namespace std; multimap<string,string> doAnagrams(const vector<string> &words) { multimap<string,string> keywords; string word; vector<string>::const_iterator viter; for(viter = words.begin();viter != words.end();viter++) { word = *viter; sort(word.begin(),word.end()); keywords.insert(make_pair(word,*viter)); } return keywords; } void main() { vector<string> words; string word; while(cin>>word) words.push_back(word); multimap<string,string> multiwords; multiwords = doAnagrams(words); typedef multimap<string,string>::const_iterator multiiter_t; multiiter_t multiiter; //遍历multimap,与map的遍历有较大不同 //注意multimap的底层实现,在键相同时,一样在红黑树中进行插入操作 for(multiiter = multiwords.begin();multiiter != multiwords.end();) { //equal_range返回一对迭代器,分别指向该键所对应的值区间[ ) pair<multiiter_t,multiiter_t> pos = multiwords.equal_range(multiiter->first); while(pos.first != pos.second) { cout<<pos.first->second<<" "; ++pos.first; multiiter++; } cout<<endl; } system("pause"); }
不用multimap使用map<string,vector<string> >也是一个方案存储相同的变位词。
二、习题:
习题2-1:
在可以进行预处理的情况下,计算出每个单词的标识,并排序。在查询时用二分搜索。
习题2-2:
方法一:二分搜索通过递归搜索包含半数以上整数的子区间来查找至少出现两次的单词。因为4300000000 > 2^32,所以必定存在重复的整数,搜索范围从[0, 2^32)开始,中间值mid为2^31,若区间[0, 2^31)内的整数个数大于2^31个,则调整搜索区间为[0, 2^31),反之则调整搜索区间为[2^31, 2^32),然后再对整个文件再遍历一遍,直到得到最后的结果。这样一共会有log2(n)次的搜索,每次遍历n个整数(每次都是完全遍历),总体的复杂度为o(nlog2(n))。
http://blog.csdn.net/silenough/article/details/7040028
方法二:二叉搜索,每读取一定数据后,先判断首位数据加上数据长度是否等于尾部数据,如果相等,继续读取文件,如果不等,进行搜索,比较中位值与尾部数据+首位数据。如果中位值大,则继续在尾部二分查找,如果中位值小,则继续头部二分查找,如果相等,那么这个数就是重复的数。
【http://blog.csdn.net/qjzl2008/article/details/7707598】答案来自该链接但是没看懂,谁看懂了教我啊(*^__^*) 嘻嘻……
习题2-3:
海豚法(说明:http://blog.csdn.net/zhoushuai520/article/details/7703368):
#include <iostream> #include <assert.h> #include<string> using namespace std; static int gcd(int n,int m) { assert(n > m); return m > 0 ? gcd(m,n%m) : n; } static int gcd2(int i,int j) { while(i != j) { if(i > j) i -= j; else j -= i; } return i; } void doRotate(string &str,int rotatedist,int len) { int i,j; char t; int igcd = gcd(len,rotatedist); for(i = 0;i < igcd;i++) { t = str[i]; j = i; for( ; ; ) { int k = j + rotatedist; k %= len; if(k == i) break; str[j] = str[k]; j = k; } str[j] = t; } } void main() { string str = "abc12345"; cout<<str<<endl; doRotate(str,3,8); cout<<str<<endl; cout<<gcd(8,3)<<endl; cout<<gcd2(8,3)<<endl; system("pause"); }
习题2-9:待解
三、总结:
1 二分搜索
2 排序在非常多的问题中是必须做的前期准备工作,但是对于问题C,排序并不能合理的解决问题,但是给每条记录添加一个额外的键(标识),并按照这些键进行排序,巧妙的解决了问题。
3 标识:当使用等价关系来定义类时,定义一种标识使得类中的每一项都具有相同的标识,而该类以外的其他项则没有该标识。
参考:
http://blog.csdn.net/zhoushuai520/article/details/7703368
http://blog.csdn.net/tianshuai1111/article/details/7555563