「leetcode」78. 子集【回溯演算法】詳解!
本文 https://github.com/youngyangyang04/leetcode-master 已經收錄,裡面還有leetcode刷題攻略、各個型別經典題目刷題順序、思維導圖,可以fork到自己倉庫,有空看一看一定會有所收穫,如果對你有幫助也給一個star支援一下吧!
第78題. 子集
題目地址:https://leetcode-cn.com/problems/subsets/
給定一組不含重複元素的整數陣列 nums,返回該陣列所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
思路
求子集問題和回溯演算法:求組合問題!和回溯演算法:分割問題!又不一樣了。
如果把 子集問題、組合問題、分割問題都抽象為一棵樹的話,那麼組合問題和分割問題都是收集樹的葉子節點,而子集問題是找樹的所有節點!
其實子集也是一種組合問題,因為它的集合是無序的,子集{1,2} 和 子集{2,1}是一樣的。
那麼既然是無序,取過的元素不會重複取,寫回溯演算法的時候,for就要從startIndex開始,而不是從0開始!
有同學問了,什麼時候for可以從0開始呢?
求排列問題的時候,就要從0開始,因為集合是有序的,{1, 2} 和{2, 1}是兩個集合,排列問題我們後續的文章就會講到的。
以示例中nums = [1,2,3]為例把求子集抽象為樹型結構,如下:
從圖中紅線部分,可以看出遍歷這個樹的時候,把所有節點都記錄下來,就是要求的子集集合。
回溯三部曲
- 遞迴函式引數
全域性變數陣列path為子集收集元素,二維陣列result存放子集組合。(也可以放到遞迴函式引數裡)
遞迴函式引數在上面講到了,需要startIndex。
程式碼如下:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
- 遞迴終止條件
從圖中可以看出:
剩餘集合為空的時候,就是葉子節點。
那麼什麼時候剩餘集合為空呢?
就是startIndex已經大於陣列的長度了,就終止了,因為沒有元素可取了,程式碼如下:
if (startIndex >= nums.size()) {
return;
}
其實可以不需要加終止條件,因為startIndex >= nums.size(),本層for迴圈本來也結束了。
- 單層搜尋邏輯
求取子集問題,不需要任何剪枝!因為子集就是要遍歷整棵樹。
那麼單層遞迴邏輯程式碼如下:
for (int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]); // 子集收集元素
backtracking(nums, i + 1); // 注意從i+1開始,元素不重複取
path.pop_back(); // 回溯
}
C++程式碼
根據關於回溯演算法,你該瞭解這些!給出的回溯演算法模板:
void backtracking(引數) {
if (終止條件) {
存放結果;
return;
}
for (選擇:本層集合中元素(樹中節點孩子的數量就是集合的大小)) {
處理節點;
backtracking(路徑,選擇列表); // 遞迴
回溯,撤銷處理結果
}
}
可以寫出如下回溯演算法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;
}
};
在註釋中,可以發現可以不寫終止條件,因為本來我們就要遍歷整顆樹。
有的同學可能擔心不寫終止條件會不會無限遞迴?
並不會,因為每次遞迴的下一層就是從i+1開始的。
總結
相信大家經過了
- 組合問題:
- 分割問題:
洗禮之後,發現子集問題還真的有點簡單了,其實這就是一道標準的模板題。
但是要清楚子集問題和組合問題、分割問題的的區別,子集是收集樹形結構中樹的所有節點的結果。
而組合問題、分割問題是收集樹形結構中葉子節點的結果。
就醬,如果感覺收穫滿滿,就幫Carl宣傳一波「程式碼隨想錄」吧!
我是程式設計師Carl,可以找我組隊刷題,也可以在B站上找到我,本文leetcode刷題攻略已收錄,更多精彩演算法文章盡在公眾號:程式碼隨想錄,關注後就會發現和「程式碼隨想錄」相見恨晚!
如果感覺對你有幫助,不要吝嗇給一個?吧!
相關文章
- [LeetCode]78. 子集LeetCode
- 78. 子集
- 【LeetCode回溯演算法#07】子集問題I+II,鞏固解題模板並詳解回溯演算法中的去重問題LeetCode演算法
- LeetCode HOT 100:子集(簡單易懂的回溯)LeetCode
- 「leetcode」93.復原IP地址【回溯演算法】詳解!LeetCode演算法
- 程式碼隨想錄演算法訓練營第24天 | 93.復原IP地址 78.子集 90.子集Ⅱ演算法
- LeetCode演算法訓練-回溯總結LeetCode演算法
- LeetCode-078-子集LeetCode
- leetcode題解(遞迴和回溯法)LeetCode遞迴
- 回溯演算法 LeetCode 131 分割回文子串演算法LeetCode
- 【LeetCode回溯演算法#08】遞增子序列,鞏固回溯演算法中的去重問題LeetCode演算法
- leetcode 130被圍繞的區域 回溯演算法LeetCode演算法
- leetcode.回溯演算法能解決什麼問題?LeetCode演算法
- leetcode:全排列(java回溯)LeetCodeJava
- 【LeetCode回溯演算法#06】復原IP地址詳解(練習如何處理邊界條件,判斷IP合法性)LeetCode演算法
- 回溯演算法演算法
- 1863. 找出所有子集的異或總和再求和 JavaScript【回溯】JavaScript
- LeetCode46 回溯演算法求全排列,這次是真全排列LeetCode演算法
- LeetCode通關:連刷十四題,回溯演算法完全攻略LeetCode演算法
- 演算法-回溯演算法演算法
- 【每日一題-leetcode】416. 分割等和子集每日一題LeetCode
- leetcode:組合總和II(回溯java)LeetCodeJava
- 每日一題之拉低通過率 回溯演算法 leetcode 51 N皇后每日一題演算法LeetCode
- LeetCode刷題日記 416. 分割等和子集LeetCode
- 常用演算法之回溯法演算法
- 正規表示式學習教程之回溯引用backreference詳解
- [Leetcode]827.使用回溯+標記解決最大人工島問題LeetCode
- 圖解Leetcode組合總和系列——回溯(剪枝優化)+動態規劃圖解LeetCode優化動態規劃
- 刷題總結——回溯演算法演算法
- 回溯演算法介紹以及模板演算法
- LeetCode 55. 跳躍遊戲 ( 回溯 dp 貪心LeetCode遊戲
- Leetcode 演算法題解系列 - 最小棧LeetCode演算法
- 回溯演算法之復原IP地址演算法
- 回溯演算法求解橋本分數式演算法
- Day 22 回溯演算法 part01演算法
- ”回溯演算法“框架及練習題演算法框架
- BitMap演算法詳解演算法
- Manacher演算法詳解演算法