1. 程式人生 > 其它 >回溯演算法-子集組合排列

回溯演算法-子集組合排列

本文分享一些自己在刷回溯演算法-子集組合排列時總結的套路。

一、回溯演算法和二叉樹的聯絡

  1. 回溯演算法本質上是決策樹的選擇和撤銷過程,所以也屬於二叉樹。
  2. 回溯演算法框架中會出現for迴圈中巢狀遞迴,for是廣度搜索,遞迴是深度搜索;在二叉樹中,經常會有traverse(root.left)和traverse(root.right),其實這裡是將for迴圈給具體寫成了兩個遞迴函式進行廣度搜索,然後遞迴函式進行深度搜索。
  3. 回溯演算法放到二叉樹中說,相當於在前序位置和後序位置進行操作。

二、回溯演算法分類

分類規則:原陣列中的元素是否重複 | 陣列中的元素是否可以複用

1. 元素無重複並且不可複用

相關題目參考LeetCode78、77、46:

2. 元素有重複並且不可複用

相關題目參考LeetCode90、40、47:

3. 元素無重複並且可以複用

相關題目參考LeetCode39:

三、子集組合排列模組化

本節將子集組合排列模組化,在以後做題時根據題目要求選取模組組裝即可,需要做過一些子集組合排列相關回溯題目,對其有一定理解。

1. 回溯函式基本框架

List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();
void backtrack(arg...) {
    //根據條件將路徑加入結果集
    if(條件判斷) {
        res.add(new LinkedList<>(track));
        return;
    }
    for(迴圈條件) {
        track.add(數值);
        backtrack(arg...);
        track.removeLast();
    }
}

2. 模組1:if(條件判斷)

子集:求全部的子集,不需要條件判斷
組合:需要條件判斷,一般判斷是track.size() == k即符合組合大小K的路徑才會新增到結果集
排列:需要條件判斷,一般判斷是track.size() == nums.length即將陣列中的元素全部新增的路徑才會新增到結果集
分析:組合和排列的判斷條件本質上是一樣的,都是取某一層的結果。

3. 模組2:for(迴圈條件)

子集:for(int i=start; i<nums.length; i++) + backtrack(nums,i+1)
組合:for(int i=start; i<nums.length; i++)/for(int i=start; i<nums.length-(k-track.size())+1; i++) + backtrack(nums,i+1)
排列:for(int i=0; i<nums.length; i++) + if(used[i]) {continue;}
分析:for迴圈的功能

:1.遍歷陣列 2.在子集和組合問題和backtrack(nums,i+1)配合防止元素被重複使用。 3.在排列問題中和if(used[i]) {continue;}配合防止重複選擇

4. 模組3:arg...引數列表

子集:(1)表徵陣列:int[] nums或者int n (2)表徵start:int start
組合:(1)表徵陣列:int[] nums或者int n (2)表徵start:int start (3)表徵取第幾層集合([]集合是第一層):int k
排列:(1)表徵陣列:int[] nums

5. 模組4:假設元素可以重複

子集:(1)Arrays.sort(nums):排序,讓相同的元素靠在一起 (2)在for迴圈中假如if(i>start && nums[i] == nums[i-1]) {continue;}:i>start使nums[i-1]存在;nums[i] == nums[i-1] 跳過相同的元素
組合:同子集
排列:跟子集略有不同:if(i>start && nums[i] == nums[i-1] && !used[i-1]) {continue;}:新增!used[i-1]是為了固定元素的相對順序,防止出現重複結果

6. 模組5:假設元素可以複用

此模組與模組2+4相反,模組2的條件都是為了元素不被複用
子集:backtrack(nums,i+1)改為backtrack(nums,i),且不加模組4的條件
組合:同子集
排列:去掉used剪枝邏輯

7. 總結:

子集和組合可以歸為一類,backtrack(nums,i+1)是防止同一元素被多次使用,if(i>start && nums[i] == nums[i-1]) 是防止出現重複的結果。
排序單獨一類:if(used[i]) {continue;}是防止同一元素被多次使用,!used[i-1]是防止出現重複結果。
排序實則是對子集和組合結果的進一步劃分,所以比子集組合的條件更加苛刻。

題目連結:

leetcode-78:子集
leetcode-77:組合
leetcode-46:全排列
leetcode-90:子集II
leetcode-40:組合總和II
leetcode-47:全排列II
leetcode-39:組合總和

宣告:本文章參考了東哥的演算法小抄,隨筆中有很多地方都沒有都沒有具體解釋,主要是作為自己的回顧隨筆,如果有不清楚的,可以先看東哥的演算法小抄。