一、全排列
递归暴力DFS:
面试中,排列组合的实现是需要掌握的。一般最先想到的方法是暴力循环法,即对于每一位,遍历集合中可能的元素,如果在这一位之前出现过了该元素,跳过该元素。例如对于abc
,第一位可以是 a 或 b 或 c 。当第一位为 a 时,第二位再遍历集合,发现 a 不行,因为前面已经出现 a 了,而 b 和 c 可以。当第二位为 b 时 , 再遍历集合,发现 a 和 b 都不行,c 可以。可以用递归或循环来实现,但是复杂度为 O(nn) 。
1 void permutation1(string src, string path, vector<string>& res) { 2 if (path.size() == src.size()) { 3 res.push_back(path); 4 return; 5 } 6 int len = src.length(); 7 for (int i = 0; i < len; i++) { 8 if (path.find(src[i]) == string::npos) { 9 path.push_back(src[i]); 10 permutation1(src, path, res); 11 path.pop_back(); 12 } 13 } 14 } 15 16 vector<string> permutation(string src) { 17 vector<string> res; 18 permutation1(src, "", res); 19 return res; 20 }
交换:
首先考虑bac
和cba
这二个字符串是如何得出的。显然这二个都是abc
中的 a 与后面两字符交换得到的。然后可以将abc
的第二个字符和第三个字符交换得到acb
。同理可以根据bac
和cba
来得bca
和cab
。因此可以知道 全排列就是从第一个数字起每个数分别与它后面的数字交换,也可以得出这种解法每次得到的结果都是正确结果,所以复杂度为 O(n!)。
1 void permutation2(string src, int start, vector<string>& res) { 2 int n = src.size(); 3 if (start == n - 1) { 4 res.push_back(src); 5 return; 6 } 7 8 for (int i = start; i < n; i++) { 9 std::swap(src[start], src[i]); 10 permutation2(src, start + 1, res); 11 std::swap(src[start], src[i]); 12 } 13 }
去重的全排列
有时候给出的src中出现重复的字符,类如abb。按照上面的排列方法得出的结果里面会有重复的字符串。为了得到不一样的排列,去重的全排列要从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数
1 void permutation3(string src, int start, vector<string>& res) { 2 int n = src.size(); 3 if (start == n - 1) { 4 res.push_back(src); 5 return; 6 } 7 8 for (int i = start; i < n; i++) { 9 bool isRepeated = false; 10 for (int j = start; j < i; j++) { 11 if (src[j] == src[i]) { 12 isRepeated = true; 13 break; 14 } 15 } 16 if (!isRepeated) { 17 std::swap(src[start], src[i]); 18 permutation2(src, start + 1, res); 19 std::swap(src[start], src[i]); 20 } 21 } 22 }
二、组合
DFS暴力枚举:
1 void combination2(string src, int start, string path, vector<string>& res) { 2 int len = src.length(); 3 if (start == len) { 4 if (!path.empty()) { 5 res.push_back(path); 6 } 7 return; 8 } 9 combination2(src, start + 1, path, res); 10 path.push_back(src[start]); 11 combination2(src, start + 1, path, res); 12 } 13 14 vector<string> combination2(string src) { 15 if (src.empty()) 16 return {}; 17 vector<string> res; 18 combination2(src, 0, "", res); 19 return res; 20 }
二进制法:
1 //二进制法 2 vector<string> combination(string src) { 3 if (src.empty()) 4 return {}; 5 int len = src.length(); 6 int n = 1 << len; 7 vector<string> res; 8 for (int i = 0; i < n; i++) { 9 string tmp; 10 for (int j = 0; j < len; j++) { 11 if (i & (1 << j)) { 12 tmp.push_back(src[j]); 13 } 14 } 15 if (!tmp.empty()) { 16 res.push_back(tmp); 17 } 18 } 19 return res; 20 }