题目如下:
用n个1和m个0组成字符串,要求任意的前k个字符中,1的个数不能少于0的个数。
问满足要求的字符串数目。
解法:
该题的解法也是看了一些牛人的提示才知道的,也才知道catalan数这种东东,catalan数的一个典型应用实例跟这个题很相似,其分析过程也很有启发作用,这个应用实例是这样的:
一个栈(无穷大)的进栈序列为1,2,3,..n,有多少个不同的出栈序列?
实例分析过程:
对于每一个数来说,必须进栈一次、出栈一次。我们把进栈设为状态‘1’,出栈设为状态‘0’。n个数的所有状态对应n个1和n个0组成的2n位二进制数。由于等待入栈的操作数按照1‥n的顺序排列、入栈的操作次数b大于等于出栈的操作次数a(a≤b),因此输出序列的总数目=由左而右扫描由n个1和n个0组成的2n位二进制数,1的累计数不小于0的累计数的方案种数。
在2n位二进制数中填入n个1的方案数为c(2n,n),不填1的其余n位自动填0。从中减去不符合要求(由左而右扫描,0的累计数大于1的累计数)的方案数即为所求。
不符合要求的数的特征是由左而右扫描时,必然在某一奇数位2m+1位上首先出现m+1个0的累计数和m个1的累计数,此后的2(n-m)-1位上有n-m个 1和n-m-1个0。如若把后面这2(n-m)-1位上的0和1互换,使之成为n-m个0和n-m-1个1,结果得1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一个由n+1个0和n-1个1组成的排列。
反过来,任何一个由n+1个0和n-1个1组成的2n位二进制数,由于0的个数多2个,2n为偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在后面部分0和1互换,使之成为由n个0和n个1组成的2n位数,即n+1个0和n-1个1组成的2n位数必对应一个不符合要求的数。
因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应。
显然,不符合要求的方案数为c(2n,n+1)。由此得出 输出序列的总数目=c(2n,n)-c(2n,n+1)=1/(n+1)*c(2n,n)。
上面实例分析中的关键一点就在于要找到一个不符合方案个数的表达式(即实例中的c(2n,n+1)),而要得出此表达式则需要找到一个与不符合方案等价的0,1排列方式(即实例中把n个1,n个0中不符合方案转换为n+1个0和n-1个1的排列),这里这两者之所以能够等价,是因为n+1个0和n-1个1的所有排列都会是首先在某个基数位上出现0的累计数超过1的累计数的情况,且把后面的0,1互换之后刚好能够回归n个1,n个0的原始情况,所以与不符合方案一一对应。
对本题而言,思路完全一致,我们同样构造一个与不符合方案等价的排列,设现在有n’个1和m’个0,且(m’> n’ & n’+m’=n+m),那么对这n’个1和m’个0的所有排列也都必然是首先在某个奇数位2i+1上出现0的累计数大于1的累计数,及从1到2i+1位上有i+1个0,i个1,后面则还有m’-i-1个0和n’-i个1,同样,我们把后面的0,1对换则变成了m’-i-1个1和n’-i个0,因此总共变成了m’-i-1+i = m’-1个1,和n’-i+i+1= n’+1个0。而要使此排列与源排列中的不符合方案排列等价的话,则必须有m’-1=n,n’+1=m,进而得出m’=n+1,n’=m-1。即n个1,m个0(n>=m)的排列中不符合方案的排列与m-1个1 ,n+1个0的所有排列一一对应,所以本题的答案就是C(n+m, n) – C(m+n, n+1)。因为C(n+m, n)= C(n+m, m),C(m+n, n+1) = C(m+n, m-1),所以答案也可以是C(n+m, m)- C(m+n, m-1)等。
下面打印给定n,m的所有符合要求的01序列的代码片段,基本的算法思路是:
用n个1和m个0组成字符串,要求任意的前k个字符中,1的个数不能少于0的个数,可以利用二叉树来解决这个问题,每一个节点走左子树为1,走右字数为0,那么n+m个1或0字符构成的所有字符串个数为2^(n+m)个,那么就可以对应于一个叶节点数为2^(n+m)的满二叉树,该二叉树一共n+m+1层。所以我们可以用一个遍历满二叉树的算法来得到所有满足要求的字符串(叶节点)。下面的代码采用的是前序方式:
typedef long ( * PRESULTFETCHFunc)(
char* pszResult,
int nLen,
void* pPara
); //定义一个用于输出的回调函数,当然也可以不用这种方式
struct SAccessFootprint //记录每个节点对应的序列
{
int nPre0Count; //该节点前面的0的个数
int nPre1Count; //该节点前面的1的个数
char* pszResult; //对应的01序列字符串
SAccessFootprint(int nResultLen = 0)
{
nPre1Count = nPre0Count = 0;
if(nResultLen > 0)
{
pszResult = new char[nResultLen];
ZeroMemory(pszResult, nResultLen);
}
else
pszResult = NULL;
}
virtual ~SAccessFootprint()
{
if(pszResult)
delete [] pszResult;
}
};
typedef std::stack
size_t GenerateValid01Serail_Pre(int n1Count,
int n0Count,
PRESULTFETCHFunc pfResultFetch,
void* pCallbackPara)
{
if(n1Count < n0Count || n1Count < 0 || n0Count < 0)
return 0;
size_t resultcount = 0;
int nDepth = n1Count + n0Count;
SAccessFootprint* pFirstStep = new SAccessFootprint(nDepth + 1);
CAFTStack cStack;
cStack.push(pFirstStep);
while(cStack.size() > 0)
{
SAccessFootprint* pFootprint = cStack.top();
cStack.pop();
while(1)
{
if(pFootprint->nPre0Count + pFootprint->nPre1Count == nDepth) //先访问(访问就是查看是否是叶子节点,如果是则打印)
{
if(pfResultFetch)
pfResultFetch(pFootprint->pszResult, nDepth, pCallbackPara);
delete pFootprint;
resultcount ++;
break; //已经是叶子节点了,不可能再有子树了。
}
if(pFootprint->nPre0Count < n0Count && pFootprint->nPre0Count < pFootprint->nPre1Count) //是否有右子树
{
SAccessFootprint* pGoRight = new SAccessFootprint(nDepth + 1);
pGoRight->nPre0Count = pFootprint->nPre0Count + 1;
pGoRight->nPre1Count = pFootprint->nPre1Count;
strcpy(pGoRight->pszResult, pFootprint->pszResult);
pGoRight->pszResult[pGoRight->nPre0Count + pGoRight->nPre1Count - 1] = '0';
cStack.push(pGoRight);
}
if(pFootprint->nPre1Count < n1Count) //是否有左子树,有则继续向左走,否则跳出内循环。
{
pFootprint->nPre1Count++;
pFootprint->pszResult[pFootprint->nPre0Count + pFootprint->nPre1Count - 1] = '1';
}
else
{
delete pFootprint;
break;
}
}
}
return resultcount;
}
long PrintResult(char* pszResult, int nLen, void* pPara)
{
if(pszResult)
printf("%s/n", pszResult);
if(pPara)
(*(int*)pPara)++;
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
bool bContinue = true;
while(bContinue)
{
int n = 0, m = 0;
size_t nRe = 0;
fflush(stdout);
printf("********************0 1 serial test program*********************/n");
printf("1.Begin(Layer)/r/n2.Begin(Preorder)/r/n3.Begin(Inorder)/r/n4.Begin(Postorder)/r/n5.Exit/r/nYour select:");
int nSel;
scanf("%d", &nSel);
switch(nSel)
{
case 1:
case 2:
case 3:
case 4:
printf("Num 1 Count:");
scanf("%d", &n);
printf("Num 0 Count:");
scanf("%d", &m);
if(nSel == 1)
nRe = GenerateValid01Serail_layer(n, m, PrintResult, NULL);
else if(nSel == 2)
nRe = GenerateValid01Serail_Pre(n, m, PrintResult, NULL);
else if(nSel == 3)
nRe = GenerateValid01Serail_In(n, m, PrintResult, NULL);
else if(nSel == 4)
nRe = GenerateValid01Serail_Post(n, m, PrintResult, NULL);
if(nRe > 0)
printf("Result Count:%d/n", nRe);
break;
case 5:
bContinue = false;
break;
default:
printf("Input error!/n");
break;
}
}
return 0;
}