排列组合

参考html

https://www.cnblogs.com/graphics/archive/2011/07/14/2105195.html算法

https://zhuanlan.zhihu.com/p/79863106spa

https://zh.wikipedia.org/wiki/%E7%BB%84%E5%90%88%E6%95%B0%E5%AD%A63d

咱们在作算法题的时候,不少时候须要用到穷举,排列组合,得到所需的结果。而后再根据这个增长条件,慢慢减小判断次数,就是动态规划。因此咱们先弄清楚如何排列组合。code

给定字符串s,求出s全部可能的子串。htm

int main()
{
    string a = "abcdef";
    string b = "acdfg";
    string c = "abc";
    int n = 1 << c.size();
    for (int i = 0; i < n; i++)
    {
        int j = i;
        string tmp;
        for (auto iter = c.rbegin(); iter != c.rend(); iter++)
        {
            if (j & 1)
            {
                tmp = *iter + tmp;
            }
            j = j >> 1;
        }
        cout << tmp << endl;
    }
    char inchar;
    cin >> inchar;
}

这个解法,就是根据每个元素,都有两种状态,在子串中,或是不在子串中。那么一个n个字符串,就会有2^n个组合。用一个二进制进行判断,每次右移1,个位置上是1的表示此次存在于子串。只不过这是倒叙的,须要反过来。blog

这里的局限性就是,只能保存int的32位的字符串,若是是64位,也只能保存64个字符的字符串。再长的字符串就不行了,可是咱们考虑到64个字符的字符串的全部组合状况,已是天文数字了,在实际状况中,咱们也不会碰到。碰到以后确定有其余的解法。索引

 

问题扩展,从n个元素中,选长度是m(m<=n)子串的组合。按照你们总结的规则,c(n,m)表示从n个字符串中取m个长度全部的子串状况,那么c(n,m)=c(n-1,m) + c(n-1, m-1)。解释就是当我要判断第n个元素加进来的子串状况时,只须要知道前n-1个子串的全部状况,再加上当前子串的状况,就能够得出最后的答案。前面n-1子串的状况,能够分为,第n个计算在内,那么就是c(n-1, m-1)和第n个不计算在内就是c(n-1,m)。ip

好比abc,那么选择c(3,2)=c(3-1,2) + c(3-1, 2-1)。那么就是ab和a b,而后ab是不须要加c,a和b须要加c,就是ab ac bc。也就是计算第n个状况的时候,确定须要先计算出n-1的状况,n-1的状况中包含了m个数,n就不用计算在内;包含了m-1的状况,就把m-1全部的状况都加上第n个元素。ci

struct MNODE
{
    string substr;
    MNODE* pnext;
    MNODE()
    {
        pnext = nullptr;
    }
};
MNODE* msubstr(string& srcstr, int index, int sublen)
{
    MNODE* tmpp = nullptr;
    MNODE* prep = nullptr;
    if (index >= sublen && sublen > 0)
    {
        if (sublen == 1)
        {
            for (int i = 0; i < index; i++)
            {
                MNODE* ftpn = nullptr;
                if (tmpp == nullptr)
                {
                    tmpp = new MNODE;
                    prep = tmpp;
                    ftpn = tmpp;
                }
                else
                {
                    ftpn = new MNODE;
                    prep->pnext = ftpn;
                    prep = ftpn;
                }
                ftpn->substr = srcstr[i];
            }
        }
        else if (sublen > 1)
        {
            MNODE* nsub = msubstr(srcstr, index - 1, sublen - 1);
            tmpp = nsub;
            while (nsub != nullptr)
            {
                nsub->substr = nsub->substr + srcstr[index - 1];
                prep = nsub;
                nsub = nsub->pnext;
            }
            nsub = msubstr(srcstr, index - 1, sublen);
            prep->pnext = nsub;
            while (nsub != nullptr)
            {
                nsub = nsub->pnext;
            }
        }
    }
    return tmpp;
}
int main()
{
    string a = "abcdef";
    string b = "acdfg";
    string c = "abcd";
    MNODE* psubstr = msubstr(a, a.size(), 3);
    while (psubstr != nullptr)
    {
        cout << psubstr->substr << endl;
        MNODE* tmpp = psubstr;
        psubstr = tmpp->pnext;
        delete tmpp;
    }
    char inchar;
    cin >> inchar;
}

按照公式推导,遇到索取的子字符串是1的时候,达到边界,能够求值退出,后面在前面的基础上增长当前的字符,不断累加,到最后得出结果。

 

排列就是从n中选k,相同的元素,顺序不一样,也算不一样的一种,数学中排列的公式是

 

 

若是选取的k个元素是能够重复的,那么就是,由于每次选择都是n个元素,能够选k次,因此就是n*n*n...*n,k个n种选择相乘。

 

 

 

组合就是选择的元素,位置不一样没有影响,好比从一堆球中选择不一样颜色球的组合,数学中的组合公式是

 

 

与排列相似,由于确定仍是须要取那么多种类,只不过要把重复的去掉,就再除以k的阶。

若是是选取的元素能够重复,组合的公式就是须要额外加上重复的选择。这个公式能够转换成另外一个

 

 

那咱们上面的2^n是如何获得的呢?来源于这个公式,咱们能够知道,获取子串,虽然与顺序有关,可是,咱们不能倒序获取或是从中间任意一个位置获取,字符串是有顺序的,获取的其余顺序的字符串不能算是子串。也就至关于获取固定索引位置的字符串,只能按照原来的顺序组成一个子串,不可能出现多个,也就是组合,按照这个公式,就是2^n。

相关文章
相关标签/搜索