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