排列組合

參考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。

相關文章
相關標籤/搜索