子集問題其實就是模板題!你知道嗎?
認(rèn)識本質(zhì)之后,這就是一道模板題
子集
力扣題目鏈接:https://leetcode-cn.com/problems/subsets/
給定一組不含重復(fù)元素的整數(shù)數(shù)組 nums,返回該數(shù)組所有可能的子集(冪集)。
說明:解集不能包含重復(fù)的子集。
示例:
輸入: nums = [1,2,3]
輸出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
思路
求子集問題和77.組合和131.分割回文串又不一樣了。
如果把 子集問題、組合問題、分割問題都抽象為一棵樹的話,那么組合問題和分割問題都是收集樹的葉子節(jié)點,而子集問題是找樹的所有節(jié)點!
其實子集也是一種組合問題,因為它的集合是無序的,子集{1,2} 和 子集{2,1}是一樣的。
那么既然是無序,取過的元素不會重復(fù)取,寫回溯算法的時候,for就要從startIndex開始,而不是從0開始!
有同學(xué)問了,什么時候for可以從0開始呢?
求排列問題的時候,就要從0開始,因為集合是有序的,{1, 2} 和{2, 1}是兩個集合,排列問題我們后續(xù)的文章就會講到的。
以示例中nums = [1,2,3]為例把求子集抽象為樹型結(jié)構(gòu),如下:
子集
從圖中紅線部分,可以看出遍歷這個樹的時候,把所有節(jié)點都記錄下來,就是要求的子集集合。
回溯三部曲
- 遞歸函數(shù)參數(shù)
全局變量數(shù)組path為子集收集元素,二維數(shù)組result存放子集組合。(也可以放到遞歸函數(shù)參數(shù)里)
遞歸函數(shù)參數(shù)在上面講到了,需要startIndex。
代碼如下:
- vector<vector<int>> result;
- vector<int> path;
- void backtracking(vector<int>& nums, int startIndex) {
遞歸終止條件
從圖中可以看出:
子集
剩余集合為空的時候,就是葉子節(jié)點。
那么什么時候剩余集合為空呢?
就是startIndex已經(jīng)大于數(shù)組的長度了,就終止了,因為沒有元素可取了,代碼如下:
- if (startIndex >= nums.size()) {
- return;
- }
其實可以不需要加終止條件,因為startIndex >= nums.size(),本層for循環(huán)本來也結(jié)束了。
- 單層搜索邏輯
求取子集問題,不需要任何剪枝!因為子集就是要遍歷整棵樹。
那么單層遞歸邏輯代碼如下:
- for (int i = startIndex; i < nums.size(); i++) {
- path.push_back(nums[i]); // 子集收集元素
- backtracking(nums, i + 1); // 注意從i+1開始,元素不重復(fù)取
- path.pop_back(); // 回溯
- }
C++代碼
根據(jù)關(guān)于回溯算法,你該了解這些!給出的回溯算法模板:
- void backtracking(參數(shù)) {
- if (終止條件) {
- 存放結(jié)果;
- return;
- }
- for (選擇:本層集合中元素(樹中節(jié)點孩子的數(shù)量就是集合的大小)) {
- 處理節(jié)點;
- backtracking(路徑,選擇列表); // 遞歸
- 回溯,撤銷處理結(jié)果
- }
- }
可以寫出如下回溯算法C++代碼:
- class Solution {
- private:
- vector<vector<int>> result;
- vector<int> path;
- void backtracking(vector<int>& nums, int startIndex) {
- result.push_back(path); // 收集子集,要放在終止添加的上面,否則會漏掉自己
- if (startIndex >= nums.size()) { // 終止條件可以不加
- return;
- }
- for (int i = startIndex; i < nums.size(); i++) {
- path.push_back(nums[i]);
- backtracking(nums, i + 1);
- path.pop_back();
- }
- }
- public:
- vector<vector<int>> subsets(vector<int>& nums) {
- result.clear();
- path.clear();
- backtracking(nums, 0);
- return result;
- }
- };
在注釋中,可以發(fā)現(xiàn)可以不寫終止條件,因為本來我們就要遍歷整顆樹。
有的同學(xué)可能擔(dān)心不寫終止條件會不會無限遞歸?
并不會,因為每次遞歸的下一層就是從i+1開始的。
本文轉(zhuǎn)載自微信公眾號「代碼隨想錄」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系代碼隨想錄公眾號。