编程珠玑学习笔记 Aha算法 思考以及一些代码实现

"A problem that seems difficult may have a simple, unexpected solution"

- 一个看似难以解决的问题,或许在背后隐藏着一个简单,意想不到的解法

 

三个引例:

 

1. 输入一个连续文件,文件中存储了之多4,000,000,000个32bit的整数,并且这些整数是按照任意顺序排列的。请你找出一个没有在这个序列中的整数(为什么一定会有至少一个missing的数据呢)。

         Q1 如果给你足够大的内存,你将如何解决这个问题

         Q2. 如果仅仅给你一些可以使用的外文件,但是却仅仅几百byte的内存,你将如何解决这个问题。

 

2. 假设输入一个长度为n的字符串,请在第i个位置将字符串旋转。例如n = 8, i= 3, 数组是abcdefgh,旋转以后变成defghabc。 你能用O(n)的算法,O(1)的空间解决这个问题吗

 

3. 给出一个英文字典,里面包括单词。请你找出所有的变位词(anagrams),例如pots,stop,tops是变位词。因为组成单词的字母相同,仅仅是排列不同而已。

 

问题1.

关于第一个问题,首先我们的数据是4billion的,而32bit的数据要有 4,294,967,296所以一定是有数据丢失的。而解决这个问题的方法,如果有很大的内存可以使用bitmap辅助的计数排序的方法来找到丢失的数字,使用的内存大概是 536M。

第二个问题说如果只有几百byte的内存,和一些连续的文件,这个问题应该如何解决。

这个算法我想了半天,看了一些资料才基本搞明白。

二分原则是这样的:(问题简化,我们假设现在只有3bit的数字,这个时候我们的范围就是0-7,例如丢失的数字是3)

第一步是遍历这个文件,以最高位的值作为依据进行二分。

      例如我们的数据是(无序,并且丢失的是3)

6(110)

2(010)

0(000)

1(001)

4(100)

7(111)

5(101)

   第一步二分的结果是 file1(最高位是1,范围是100 - 111): 6 4 7 5    file2: 2 0 1.(范围是 000 - 011) 

   在分割的时候统计,如果文件中的数字的个数小于应该有的数字,这里应该是4个,那么认为里面有数据丢失。所以定位在file2中存在数字丢失。

 

第二步是在判明已经有丢失的文件中继续寻找,不过这次按照第二位进行二分

在file2 中将内容分为

file3:(2) file4:0 1

这个时候我们发现在file3中的范围内(010- 011)内缺少数字。这个时候就可以找到缺少的数字为3了。

这个算法最重要的一点是:它不是一个基于普通排序而定义的二分查找。我们在算法里找不到排序的使用,但是却能找到丢失的数字。

这个要我想到了计数排序,计数排序记录了一个区间内,每个元素值出现的个数。而这个题目是计数在某个区间内元素的个数,然后细分下去。

 

问题2:

这个编程题目其实蛮经典的,在crack code interview中也有提到。大致的思想如图所示

 

首先将a[0]存放在临时变量temp中,然后依次将 a[i] -> a[0]  a[2i] -> a[i], 当然每次使用n * i的时候都要和数组大小size取摩尔运算。最后直到循环会到a[0].

如果发现最后a[1]没有处理,然后将a[1] 放入temp重复上面的过程。数学可以证明循环使用的次数是 gcd(size, i); 其中size为描述中的n,i就是从第i个元素做rotate。

代码编写如下:

[cpp] view plain copy print ?
  1. #include   
  2.   
  3.   
  4. template<typename T>  
  5. void swap(T &a, T &b)  
  6. {  
  7.     T c = a;  
  8.     a = b;  
  9.     b = c;  
  10. }  
  11. int gcd(int m, int n)  
  12. {  
  13.     if( n > m)  
  14.     {  
  15.         swap(m, n);  
  16.     }  
  17.     int r;  
  18.     while(n)  
  19.     {  
  20.         r = m % n;  
  21.         m = n;  
  22.         n = r;  
  23.     }  
  24.     return m;  
  25. }  
  26. template<typename T>  
  27. void RotateArray(T vData[], int nSizeArray, int iRot)  
  28. {  
  29.     int nShift = gcd(nSizeArray, iRot);  
  30.   
  31.     for(int i = 0;i < nShift; i++)  
  32.     {  
  33.           
  34.         T tmp = vData[i];  
  35.         int cur = i, nxt;  
  36.   
  37.         int j = 0;  
  38.         while(1)  
  39.         {  
  40.             nxt = ((j + 1) * iRot + i) % nSizeArray;  
  41.             cur = ((j) * iRot + i) % nSizeArray;  
  42.             if(nxt == i)  
  43.             {  
  44.                 break;  
  45.             }  
  46.             vData[cur] = vData[nxt];  
  47.             j++;  
  48.         }  
  49.         vData[cur] = tmp;  
  50.     }  
  51.   
  52. }  
  53.   
  54. int main()  
  55. {  
  56.     char s[10] = "abcdefghi";  
  57.     RotateArray(s, 9, 5);  
  58.     printf(s);  
  59.     return 0;  
  60. }  
 

关于第二种解法:

假如待旋转的数组是x,可以将数组分成xy看待,a是前面的i个元素,i表示开始旋转那个元素。

例如我们的 abcdefg 如果i为3 那么 x = abc     y = defg。这样的话问题的本质可以理解为 将xy通过交换变成 yx。

假设y的长度比x要长,所以可以将其表示为 x(yl)(yr),并且约定yr的长度和x一样。交换x和yr部分得到(yr)(yl)x,下面我们要做的是将(yr)(yl)交换,变成(yl)(yr)。这个时候就可以用递归的思路来求解了。

[cpp] view plain copy print ?
  1. template<typename T>  
  2. void RotateArray(T vData[], int f, int m, int t)  
  3. {  
  4.     int nL = m - f;  
  5.     int nR = t - m + 1;  
  6.     int i,j;  
  7.       
  8.     if( nL > nR )  
  9.     {  
  10.         for(i = f, j = m; j <= t; i++, j++)  
  11.         {  
  12.             swap(vData[i], vData[j]);  
  13.         }  
  14.              RotateArray(vData, f + nR, m, t);  
  15.     }  
  16.     else if(nL < nR)  
  17.     {  
  18.         for(i = f, j = t - nL + 1; i < m; i++, j++)  
  19.         {  
  20.             swap(vData[i], vData[j]);  
  21.         }  
  22.              RotateArray(vData, f, m, t - nL);  
  23.     }  
  24.     else if(nL == nR)  
  25.     {  
  26.         for(i = f, j = t; i < j; i++,j--)  
  27.         {  
  28.             swap(vData[i], vData[j]);  
  29.         }  
  30.     }  
  31. }  

解法3. 不过这个代码确实没有aha的感觉,也许应该叫ouch吧,没错。利用上面的方法确实需要心思细腻的编程,事实上可以有更aha的算法

将ab变成ba,其实可以用如下方式来做操作,首先将 reverse(a), reverse(b), 最后reverse(ab)。哈哈 是不是有aha的感觉了。

 

问题三:

这个题目是我的第一感觉是用哈希表,将每个字符串求一个哈希值。但是我没有特别想通如何将 spot和pots映射到同一个哈希的slot里。

作者给出的思路其实也满类似。

1. 为每个单词做签名

2. 根据签名排序。

而作者在这里所使用的签名也是很简单的方法,就是一个单词的字母序的重排。 例如将spot -> opst 而pots的签名也是opst,所以他们会有相同的排名。

如果是我设计数据结构,我会写成

 

struct WordPair

{

      char strWord[MAX_PATH];

      char strSigniture[MAX_PATH];

}

程序第一步输入所有的字符串,同时求出所有的signiture, 然后按照签名排序,就可以得到同构的词汇了。

 

这给我的哈希法一些提示,如果先排序,然后再哈希不就是我那个的解决方案了吗~

 

 

你可能感兴趣的:(面试,算法)