"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。
代码编写如下:
关于第二种解法:
假如待旋转的数组是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)。这个时候就可以用递归的思路来求解了。
解法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, 然后按照签名排序,就可以得到同构的词汇了。
这给我的哈希法一些提示,如果先排序,然后再哈希不就是我那个的解决方案了吗~