參考
https://www.cnblogs.com/graphics/archive/2011/07/14/2105195.html
https://zhuanlan.zhihu.com/p/79863106
https://zh.wikipedia.org/wiki/%E7%BB%84%E5%90%88%E6%95%B0%E5%AD%A6
我們在做算法題的時候,很多時候需要用到窮舉,排列組合,獲得所需的結果。然后再根據這個增加條件,慢慢減少判斷次數,就是動態規划。所以我們先弄清楚如何排列組合。
給定字符串s,求出s所有可能的子串。
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的表示這次存在於子串。只不過這是倒敘的,需要反過來。
這里的局限性就是,只能保存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)。
比如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個元素。
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。