问题描述:一串首尾相连的珠子(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