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