Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
For example,
If n = 4 and k = 2, a solution is:
[ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
這道題讓求1到n共n個數字里k個數的組合數的所有情況,還是要用深度優先搜索DFS來解,根據以往的經驗,像這種要求出所有結果的集合,一般都是用DFS調用遞歸來解。那么我們建立一個保存最終結果的大集合res,還要定義一個保存每一個組合的小集合out,每次放一個數到out里,如果out里數個數到了k個,則把out保存到最終結果中,否則在下一層中繼續調用遞歸。網友u010500263的博客里有一張圖很好的說明了遞歸調用的順序,請點擊這里。根據上面分析,可寫出代碼如下:
解法一:
class Solution { public: vector<vector<int>> combine(int n, int k) { vector<vector<int>> res; vector<int> out; helper(n, k, 1, out, res); return res; } void helper(int n, int k, int level, vector<int>& out, vector<vector<int>>& res) { if (out.size() == k) {res.push_back(out); return;} for (int i = level; i <= n; ++i) { out.push_back(i); helper(n, k, i + 1, out, res); out.pop_back(); } } };
對於n = 5, k = 3, 處理的結果如下:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
我們再來看一種遞歸的寫法,此解法沒用helper當遞歸函數,而是把本身就當作了遞歸函數,寫起來十分的簡潔,也是非常有趣的一種解法。這個解法用到了一個重要的性質 C(n, k) = C(n-1, k-1) + C(n-1, k),這應該在我們高中時候學排列組合的時候學過吧,博主也記不清了。總之,翻譯一下就是,在n個數中取k個數的組合項個數,等於在n-1個數中取k-1個數的組合項個數再加上在n-1個數中取k個數的組合項個數之和。這里博主就不證明了,因為我也不會,就直接舉題目中的例子來說明吧:
C(4, 2) = C(3, 1) + C(3, 2)
我們不難寫出 C(3, 1) 的所有情況:[1], [2], [3],還有 C(3, 2) 的所有情況:[1, 2], [1, 3], [2, 3]。我們發現二者加起來為6,正好是 C(4, 2) 的個數之和。但是我們仔細看會發現,C(3, 2)的所有情況包含在 C(4, 2) 之中,但是 C(3, 1) 的每種情況只有一個數字,而我們需要的結果k=2,其實很好辦,每種情況后面都加上4,於是變成了:[1, 4], [2, 4], [3, 4],加上C(3, 2) 的所有情況:[1, 2], [1, 3], [2, 3],正好就得到了 n=4, k=2 的所有情況了。參見代碼如下:
解法二:
class Solution { public: vector<vector<int>> combine(int n, int k) { if (k > n || k < 0) return {}; if (k == 0) return {{}}; vector<vector<int>> res = combine(n - 1, k - 1); for (auto &a : res) a.push_back(n); for (auto &a : combine(n - 1, k)) res.push_back(a); return res; } };
我們再來看一種迭代的寫法,也是一種比較巧妙的方法。這里每次先遞增最右邊的數字,存入結果res中,當右邊的數字超過了n,則增加其左邊的數字,然后將當前數組賦值為左邊的數字,再逐個遞增,直到最左邊的數字也超過了n,停止循環。對於n=4, k=2時,遍歷的順序如下所示:
0 0 #initialization 1 0 1 1 1 2 #push_back 1 3 #push_back 1 4 #push_back 1 5 2 5 2 2 2 3 #push_back 2 4 #push_back ... 3 4 #push_back 3 5 4 5 4 4 4 5 5 5 #stop
解法三:
class Solution { public: vector<vector<int>> combine(int n, int k) { vector<vector<int>> res; vector<int> out(k, 0); int i = 0; while (i >= 0) { ++out[i]; if (out[i] > n) --i; else if (i == k - 1) res.push_back(out); else { ++i; out[i] = out[i - 1]; } } return res; } };
類似題目:
參考資料:
https://leetcode.com/problems/combinations/description/
https://leetcode.com/problems/combinations/discuss/27015/3-ms-Java-Solution
https://leetcode.com/problems/combinations/discuss/27002/Backtracking-Solution-Java
https://leetcode.com/problems/combinations/discuss/26992/Short-Iterative-C++-Answer-8ms