完全理解回溯法的精要

給定一個沒有重複數字的序列,返回其全部可能的全排列。
示例:算法

輸入: [1,2,3]
輸出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

問題分析

使用什麼方法?

全排列很明顯使用回溯法來進行解答數組

什麼是回溯法?

回溯法(探索與回溯法)是一種選優搜索法,又稱爲試探法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步從新選擇,這種走不通就退回再走的技術爲回溯法,而知足回溯條件的某個狀態的點稱爲「回溯點」。函數

怎麼使用回溯法?

運用回溯法解題的關鍵要素有如下三點:設計

  1. 針對給定的問題,定義問題的解空間;
  2. 肯定易於搜索的解空間結構;
  3. 深度優先方式搜索解空間,而且在搜索過程當中用剪枝函數避免無效搜索。code

    什麼是深度優先搜索?

    深度優先搜索(縮寫DFS)有點相似廣度優先搜索,也是對一個連通圖進行遍歷的算法。它的思想是從一個頂點V0開始,沿着一條路一直走到底,若是發現不能到達目標解,那就返回到上一個節點,而後從另外一條路開始走到底,這種儘可能往深處走的概念便是深度優先的概念。對象

代碼模板是什麼樣子的?

void BackTrace(int t) {
    if(t>n)
        Output(x);
    else
    for(int i = f (n, t); i <= g (n, t); i++ ) {
        x[t] = h(i);
        if(Constraint(t) && Bound (t))
        BackTrace(t+1);
    }
}

其中,t表示遞歸深度,即當前擴展結點在解空間樹中的深度;n用來控制遞歸深度,即解空間樹的高度。當t>n時,算法已搜索到一個葉子結點,此時由函數Output(x)對獲得的可行解x進行記錄或輸出處理遞歸

f(n, t)g(n, t)分別表示在當前擴展結點處未搜索過的子樹的起始編號和終止編號;h(i)表示在當前擴展結點處x[t]的第i個可選值;函數Constraint(t)Bound(t)分別表示當前擴展結點處的約束函數和限界函數。若函數Constraint(t)的返回值爲真,則表示當前擴展結點處x[1:t]的取值知足問題的約束條件;不然不知足問題的約束條件。若函數Bound(t)的返回值爲真,則表示在當前擴展結點處x[1:t]的取值還沒有使目標函數越界,還需由BackTrace(t+1)對其相應的子樹作進一步地搜索;不然,在當前擴展結點處x[1:t]的取值已使目標函數越界,可剪去相應的子樹。rem

回溯法的具體實施

class Solution {
    public List<List<Integer>> permute(int[] nums) {
      //LeetCode代碼模板  
    }
}

step 1 定義問題的解空間

什麼是解空間?

應用回溯法求解問題時,首先應明肯定義問題的解空間,該解空間應至少包含問題的一個最優解。例如,對於有n種物品的 0-1 揹包問題,其解空間由長度爲n0-1 向量組成,該解空間包含了對變量的全部可能的0-1 賦值。當n=3時,其解空間是{ (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1) }
在定義了問題的解空間後,還須要將解空間有效地組織起來,使得回溯法能方便地搜索整個解空間,一般將解空間組織成樹或圖的形式。例如,對於n= 30-1 揹包問題,其解空間能夠用一棵徹底二叉樹表示,從樹根到葉子結點的任意一條路徑可表示解空間中的一個元素,如從根結點A到結點J的路徑對應於解空間中的一個元素(1, 0, 1)數學

定義本題的解空間

全排列問題,由於輸入數組的長度爲n = nums.length,解空間就是一個森林:

這裏須要一個森林的圖
假設n=4nums[]={1,2,3,4}則解空間應該是
第一層:1 2 3 4
第二層:12 13 14 /21 23 24/31 32 34
第三層:123 124/132 134/213 214/231 234/241 243/312 314/.....
第四層:略

肯定易於搜索的解空間結構

解空間主要對應的是子集樹和排列樹,依據題意進行選擇。(根據題意畫個圖,就知道了)

什麼是子集樹???

子集樹是一個數學學科詞彙,屬於函數類,當所給問題是從n個元素的集合S中找出S知足某種性質的子集時,相應的解空間稱爲子集樹。
當所給問題是從n個元素的集合S中找出S知足某種性質的子集時,相應的解空間稱爲子集樹。例如:n個物品的0-1揹包問題所相應的解空間是一棵子集樹,這類子集樹一般有2^n個葉結點,其結點總數爲(2^(n+1))-1。遍歷子集樹的算法一般需O(2^n)計算時間。

什麼是排列樹??

當所給問題是肯定n個元素知足某種性質的排列時,相應的解空間樹稱爲排列樹。排列樹一般有n!個葉子節點。所以遍歷排列樹須要O(n!)的計算時間。

上面已經肯定,要將解空間構建成子集樹的形式

step 2 回溯法的精髓

回溯的精髓

退回原狀態
如何回退是回溯的精髓,何時回退
就本題而言,第一躺全排列應該是1->2->3->4 ,當走到最後一步4以後,應該回退一步到1->2->3由於3只有一個分支4,再回退一步到1->2,而後知足了約束函數能夠進行下一步1->2->4;
對於本題,回退到方法在於,標記未被訪問的數組下標,回退則重製標記
所以能夠使用一個visited[]數組,數組的長度爲nums.length,被訪問則對應的下標標記爲true,不然標記爲false

step 3 回溯函數的設計

void BackTrace(int t)只傳遞一個參數的話顯然是沒法知足本題的,由於本題包含了一下5個須要傳遞的參數:

  1. visited[] 數組;
  2. t 遞歸深度;
  3. List<List<Integer>> output 保存全部解的大容器
  4. List<Integer> save 保存解的小容器
  5. nums[]原始數據

所以,BackTrace應設計爲:

public static void BackTrace( List<Integer> save, List<List<Integer>> out, boolean visited[], int nums[]) {
        if (save.size() == nums.length) {
            out.add(new ArrayList<>(save));
            return;
        } else
            for (int i = 0; i < nums.length; i++) {
                if (visited[i]) continue;
                visited[i] = true;
                save.add(nums[i]);
                BackTrace( save, out, visited, nums);
                save.remove(save.size() - 1);
                visited[i] = false;
            }
    }

怎麼寫出這段代碼須要結合前面的內容反覆的思考 =-= 我想了很久才理清楚回溯的思路

回溯法的延伸

子集問題
題目:

給定一組不含重複元素的整數數組 nums,返回該數組全部可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3] 輸出: [ [3],   [1],   [2],   [1,2,3],   [1,3],   [2,3],   [1,2],   [] ]
從上題中咱們能夠得出結論,這仍然是一道須要使用回溯法的題目。

解空間與解空間結構

很明顯這是一個子集數的解空間結構

假設n=3nums[]={1,2,3}則解空間應該是
第一層:1 2 3
第二層:12 13/21 23/31 32
第三層:123 132/213 231/312 321/

關鍵性問題

  1. 經過什麼方法回退?
  2. 約束條件是什麼?
  3. 去除重複對象
檢測重複

檢測重複首先想到的會是哈希表HashMap.所以每一次添加都應該在添加以前查找,若是找到重複則不存入;

約束條件是什麼

約束條件應該仍是當遍歷到最後一個元素時退出?

經過什麼方法回退?

因爲集合的特殊性。不須要回退;

函數的設計:

public static void BackTrack(int t,int[] nums, List<List<Integer>> out, List<Integer> save) {

        out.add(new ArrayList<>(save));

        for (int i = t; i < nums.length; i++) {

            save.add(nums[i]);

            BackTrack(i+1,nums, out, save);

            save.remove(save.size()-1);
        }
    }
相關文章
相關標籤/搜索