您的位置:首页 > 编程语言

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

2012-07-19 18:48 567 查看
"Aproblemthatseemsdifficultmayhaveasimple,unexpectedsolution"

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

三个引例:

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):6475file2:201.(范围是000-011)

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

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

在file2中将内容分为

file3:(2)file4:01

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

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

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

问题2:

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



首先将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]
viewplaincopyprint?

#include<stdio.h>

template<typenameT>
voidswap(T&a,T&b)
{
Tc=a;
a=b;
b=c;
}
intgcd(intm,intn)
{
if(n>m)
{
swap(m,n);
}
intr;
while(n)
{
r=m%n;
m=n;
n=r;
}
returnm;
}
template<typenameT>
voidRotateArray(TvData[],intnSizeArray,intiRot)
{
intnShift=gcd(nSizeArray,iRot);

for(inti=0;i<nShift;i++)
{

Ttmp=vData[i];
intcur=i,nxt;

intj=0;
while(1)
{
nxt=((j+1)*iRot+i)%nSizeArray;
cur=((j)*iRot+i)%nSizeArray;
if(nxt==i)
{
break;
}
vData[cur]=vData[nxt];
j++;
}
vData[cur]=tmp;
}

}

intmain()
{
chars[10]="abcdefghi";
RotateArray(s,9,5);
printf(s);
return0;
}


关于第二种解法:

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

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

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

[cpp]
viewplaincopyprint?

template<typenameT>
voidRotateArray(TvData[],intf,intm,intt)
{
intnL=m-f;
intnR=t-m+1;
inti,j;

if(nL>nR)
{
for(i=f,j=m;j<=t;i++,j++)
{
swap(vData[i],vData[j]);
}
RotateArray(vData,f+nR,m,t);
}
elseif(nL<nR)
{
for(i=f,j=t-nL+1;i<m;i++,j++)
{
swap(vData[i],vData[j]);
}
RotateArray(vData,f,m,t-nL);
}
elseif(nL==nR)
{
for(i=f,j=t;i<j;i++,j--)
{
swap(vData[i],vData[j]);
}
}
}

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

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

问题三:

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

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

1.为每个单词做签名

2.根据签名排序。

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

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

structWordPair

{

charstrWord[MAX_PATH];

charstrSigniture[MAX_PATH];

}

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

这给我的哈希法一些提示,如果先排序,然后再哈希不就是我那个的解决方案了吗~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐