題目相關
題目鏈接
LeetCode中國,https://leetcode-cn.com/problems/subsets/。
題目描述
給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例
輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
題目分析
LeetCode 給出本題難度中等。
題意分析
根據輸入的數列,生成所有的子集。很明顯,這是一個回溯算法題目。
方案1
樣例數據分析
本題的核心就是如何生成子集。也就是說利用回溯算法生成所有的子集。下面我們來分析一下樣例數據。樣例數據爲 [1, 2, 3],數列的長度爲 3,那麼我們的任務就是如何生成所有的子集。
我們可以考慮使用一個循環來生成子集。什麼意思呢,這個循環中,表示從第 i 個位置開始,到數列的結束,依次生成子集。循環代碼如下:
for (int i=pos; i<nums.size(); i++) {
path.push_back(nums[i]); //加入狀態
dfs(nums, path, i+1); //進一步搜索
path.pop_back(); //回溯
}
下面我們用一張圖來說明一下對應的輸入數據的搜索過程:
從上圖,我們可以清晰的看到,如何從空集開始,逐步搜索的過程。
算法設計
參考輸入樣例數據分析。
搜索函數設計
設計搜索函數的核心就是確定要幾個參數。碰到這個問題,我們先不管三七二十一,將函數寫上去,然後再逐步確定需要幾個參數。這是寫回溯算法的套路。
1、一個數組,用來當前的所有數據集。
2、一個數組,用來當前的已經生成的子集。
3、一個整數,表示位置信息,即在所有數據集的索引。
這樣,我們就可以設計出一個帶有 3 個參數的搜索函數,原型如下。
void dfs(vector<int>& nums, vector<int> path, int pos);
搜索返回條件
這題的返回條件爲所有數據搜索完畢。
回溯
套路回溯即可,即套用回溯算法模板即可。
AC 參考代碼
class Solution {
public:
vector<vector<int>> ans;
/*
參數1:
參數2:
參數3:從pos位置開始選
*/
void dfs(vector<int>& nums, vector<int> path, int pos) {
ans.push_back(path);
for (int i=pos; i<nums.size(); i++) {
path.push_back(nums[i]);
dfs(nums, path, i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> path;
dfs(nums, path, 0);
return ans;
}
};
方案 2
當然,我們還可以套用標準的回溯模板來實現。也就是搜索的方法不一樣,用一個比較容易理解的方法進行搜索,就是每次搜索按個數進行。第一次搜索所有 0 個數字子集,第二次搜索所有 1 個數字子集,第三次搜索所有 2 個數字字節,...,最後一次搜索所有 n 個數據子集。
樣例數據分析
這樣,我們可以繪製出對應的樣例數據搜索過程。
其他地方就不寫了,基本是雷同的。
AC 參考代碼
class Solution {
public:
vector<vector<int>> ans;
int n, k;
/*
參數1:
參數2:
參數3:從pos位置開始選
*/
void dfs(vector<int>& nums, vector<int> path, int pos) {
//退出條件
if (path.size() == k) {
//搜索到k個
ans.push_back(path);
return;
}
for (int i=pos; i<n; i++) {
path.push_back(nums[i]);
dfs(nums, path, i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
n = nums.size();
vector<int> path;
for (k=0; k<=n; k++) {
dfs(nums, path, 0);
}
return ans;
}
};
由於這樣的搜索 O(n*2^n),所以和第一個方案比對,明顯效果差了很多。但是這個方案更加容易理解。