LeetCode46全排列(數組中不含重複元素)

全排列>>>
在這裏插入圖片描述

回溯加遞歸:每次選擇都是從之前選擇好的數據中排除,再次進行選擇,因此需要一個數組標記,已經選擇了哪些數據,若之前選過,則剪枝跳過;使用編程的方法得到全排列,就是在這樣的一個樹形結構中進行編程,具體來說,就是執行一次深度優先遍歷,從樹的根結點到葉子結點形成的路徑就是一個全排列。

在這裏插入圖片描述



package BDyNamicProgramming;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author Zhou  jian
 * @Date 2020 ${month}  2020/4/25 0025  15:08
 */
public class Problem46 {


    /**
     * https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/
     * 使用編程的方法得到全排列,就是在這樣的一個樹形結構中進行編程
     * 具體來說:就是執行一次深度優先遍歷,從樹的根節點到葉子節點形成的路徑是一個全排列
     *
     *       每一個節點都表示了全排列問題求解的不同階段,這些階段通過變量的不同的值體現
     *       這些變量的不同的值,也稱爲狀態
     *       使用深度優先遍歷有回頭的過程,在回頭以後,狀態變量需要設置成和先前一樣
     *       因此在回到上一層節點的過程中,需要撤銷上一次的選擇,這個操作也稱爲狀態重置哦
     *       深度優先遍歷,可以直接藉助系統棧空間,爲我怕你們保存所需要的狀態變量,在編碼中只需要注意遍歷到相應的節點是‘
     *       狀態變量的值是正確的,具體的做法是:往下走一次的時候,path變量在尾部追加,而往回走的時候,需要撤銷上一次左的選擇,也就是在尾部操作,因此
     *

            回溯算法會大量應用“剪值”技巧以達到加快速度,有些時候,需要做一些預處理工作(例如排序)才能達到剪值的目的

            預處理工作雖然也耗時,但一般而且能夠剪值節約時間更多

     我做題的時候,第 1 步都是先畫圖,畫圖是非常重要的,只有畫圖才能幫助我們想清楚遞歸結構,想清楚如何剪枝。就拿題目中的示例,想一想人手動操作是怎麼做的,一般這樣下來,這棵遞歸樹都不難畫出。

     即在畫圖的過程中思考清楚:

     1、分支如何產生;

     2、題目需要的解在哪裏?是在葉子結點、還是在非葉子結點、還是在從跟結點到葉子結點的路徑?

     3、哪些搜索是會產生不需要的解的?例如:產生重複是什麼原因,如果在淺層就知道這個分支不能產生需要的結果,應該提前剪枝,剪枝的條件是什麼,代碼怎麼寫?

     作者:liweiwei1419
     鏈接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/
     來源:力扣(LeetCode)
     著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。






     */
    /**
     *
     * 1、首先這顆樹除了根節點和葉子節點以外,每個節點左的事情其實一樣,即在已經選擇了一些數據的前提,
     * 我們需要在剩下還沒有選擇的數中按照順序依次選擇一個數
     *
     * 2、遞歸的終止條件,數已經選購了,因此我們需要一個變量來表示當前遞歸到第幾層,我們把這個變量叫做depth
     *
     * 3、這些節點實際上表示了搜索(查找)全排列問題的不同階段,爲了區分這些不同階段,我們就需要一些變量來記錄爲了
     * 得到一個圈排列,程序進行到哪一步了,在這裏我們需要設置兩個變量:
     *
     * ··(1)已經選好了哪些樹,到葉子節點時候,這些已經選擇數就構成了一個圈排列
     *   (2) 一個布爾數組used,初始化的時候都爲fasle,表示這些樹都還沒有被選擇,當我們選定一個數的時候,就將這個
     *   數組的相應位置設置爲true,這樣在考慮下一個位置的時候,就能夠以O(1)的時間複雜度判斷這個數是否已經被選擇過
     *   這是一種以“空間換時間的”思想
     *
     *     我們把這兩個變量稱爲“狀態變量”,他們表示在我們求解一個問題的時候所處的階段
     *
     * 4、在非葉子結點處,產生不同的分支,這一操作的語義是:在還未選擇的數中一次選擇一個元素作爲下一個位置的元素,這顯然通過一個循環實現
     *
     * 5、另外,因爲是執行深度優先遍歷,從較深層次的節點返回到較淺層次的接地那的時候,需要左狀態充值,即回到過去
     *
     * @param nums
     * @return
     */

    public List<List<Integer>> permute(int[] nums){

        List<List<Integer>> rs = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        
        //標記之前遞歸的過程中 數組的哪些位置已經使用過
        boolean[] used = new boolean[nums.length];

        fun(nums,path,rs,used,0);

        return rs;


    }


    public void fun(int[] nums,List<Integer> path,List<List<Integer>> rs,boolean[] used,int depth){

        if(depth==nums.length){
            rs.add(new ArrayList<>(path));
        }

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

            //當前nums[i]在前面被使用過則進行剪枝
            if(used[i]){
                continue;
            }

            path.add(nums[i]);
            used[i]=true;

            fun(nums,path,rs,used,depth+1);
            //注意這裏是狀態重置,是從深層次節點回到淺層次節點的過程,代碼在形式上和之前是對成發的
            used[i]=false;
            path.remove((Object)nums[i]);

        }







    }


}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章