问题描述:一串首尾相连的珠子(n个),有N种颜色(N<=10),设计一个算法,取出其中一段,要求包含所有N种颜色,并使长度最短。并分析时间复杂度与空间复杂度。
思路:可以利用一种计数的方法。定义两个指针p1和p2,主要有三个步骤:
(1)p1向前移动,如果p1所指的珠子颜色编号为 i ,则增加 i 的出现次数。当出现的颜色种数为N时,p1停止。
(2)p2向前移动,如果p2所指的珠子颜色编号为 i ,则减少 i 的出现次数。当出现的颜色种数减为N-1时,p2停止。
(3)p1和p2所指的这段珠子,包含了N种颜色。如果比当前的最小段更短,则进行更新。回到(1)继续,循环终止条件为p2指向最后一个珠子。
以上给出了实现的思路,具体实现时应该注意一点,这串珠子应该是环状的。时间复杂度为O(n),空间复杂度为O(N)。
参考代码:
const int N = 5; //颜色种类
//函数功能 : 找目标珠子,珠子的颜色用编号表示
//函数参数 : pBead指向珠子数组,n为珠子个数,from为开始位置,to为结束位置
//返回值 : 找到为真,否则为假
bool BeadProblem(int *pBead, int n, int &from, int &to)
{
if(pBead == NULL || n < N)
return false; //参数存在问题,找不到
int count[N] = {0}; //用于计数
int minlen = n+1;
int i = 0, j = -1, cnt = 0;
while(j < n - 1) //j = n-1时,指向最后一个珠子
{
if(++count[pBead[i]] == 1 && ++cnt == N) //步骤1
{
for(j = j + 1; j < n && --count[pBead[j]]; j++) //步骤2
;
if(j == n)
j--; //已越过最后位置,退一步
//步骤3
int len = (j > i) ? i-j+1+n: i-j+1; //长度计算公式
if(len < minlen) //更新长度,以及起始结束位置
{
to = i;
from = j;
minlen = len;
}
cnt--; //颜色种数减1
}
i = (i + 1) % n; //考虑了环的特性,不能是i++
if(i == 0 && j == -1) //i回到了初始位置,但是j未更新,即没有找到符合条件的段
break;
}
return minlen != n+1;
}
有几个地方解释一下:第一,if(++count[pBead[i]] == 1 && ++cnt == N) 这个判断语句看似很复杂。具体是这样执行的,首先增加当前珠子颜色的出现次数。如果不是1,根据布尔判断的短路原则,++cnt 不会执行。如果是1,表明第一次出现,需要增加出现颜色的种数,因此执行++cnt。如果cnt增加到N表示找到了一段珠子,包含所有N种颜色。接着进入if语句,尽可能缩短。
第二,for(j = j + 1; j < n && --count[pBead[j]]; j++) 这句for循环语句。为什么j 初始时要加1呢?可以这样理解,假设之前已经找到了一段珠子,那么j指向珠子的末端。现在又找到一段,这一段珠子的末端应该从j + 1开始计算,不应包含j。
下面给出一段测试程序及一些测试结果。
int main()
{
int bead[] = {3,3,4,0,1,1,2,2,3,4,0,1,1,2,3,4};
int from, to;
int n = sizeof(bead)/sizeof(int);
if(BeadProblem(bead, n, from, to))
{
int i;
for(i = from; i != to; i = (i+1)%n)
cout<<bead[i]<<' ';
cout<<bead[i]<<endl;
}
return 0;
}
// 输入 输出
//{0,1,2,3,4} {0,1,2,3,4}
//{1,1,3,3,1,0,3,4,1,1,1,2,2,3,4} {0,3,4,1,1,1,2}
//{1,0,1,3,3,1,0,3,4,1,1,1,2,2,3,4} {2,3,4,1,0}
//{1,1,0,3,1,2,3,4,1,1,1,2,2,3,4} {0,3,1,2,3,4}
//{3,3,4,0,1,1,2,2,3,4,0,1,1,2,3,4} {2,3,4,0,1}
//{1,1,3,3,1,1,3,4,1,1,1,2,2,3,4} 找不到
本人享有博客文章的版权,转载请标明出处 http://blog.csdn.net/wuzhekai1985