回溯法的核心是回溯。在搜索到某一节点的时候,如果我们发现目前的节点(及其子节点)并不是需求目标时,我们回退到原来的节点继续搜索,并且把在目前节点修改的状态还原。这样的好处是我们可以始终只对图的总状态进行修改,而非每次遍历时新建一个图来储存状态。

在具体的写法上,它与普通的深度优先搜索一样,都有 [修改当前节点状态]→[递归子节
点] 的步骤,只是多了回溯的步骤,变成了 [修改当前节点状态]→[递归子节点]→[回改当前节点
状态]。

结合题目加代码注释去理解回溯更加清晰

class Solution {
    public List<List<Integer>> permute(int[] nums) {
         int len=nums.length;
         //使用一个动态数组res保存所有可能的全排列
         List<List<Integer>> res=new ArrayList<>();
          
          if(len==0){
              return res;
          }

          boolean[] used=new boolean[len];
          List<Integer> path=new ArrayList<>();

          dfs(nums,len,0,path,used,res);
          return res;
    }
     //path:往下走一层的时候,path 变量在尾部追加,
      //而往回走的时候,需要撤销上一次的选择,也是在尾部操作,因此 path 变量是一个栈;

      //depath(index):表示当前程序递归到第几层,表示当前要确定的是某个全排列中下标为 index 的那个数是多少;
    
    //布尔数组 used,初始化的时候都为 false 表示这些数还没有被选择,当我们选定一个数的时候,就将这个数组的相应位置设置为 true
    //考虑下一个位置的时候,就能够以 O(1)O(1) 的时间复杂度判断这个数是否被选择过,这是一种「以空间换时间」的思想。
    private void dfs(int[]nums,int len,int depath,List<Integer>path,boolean[]used,List<List<Integer>> res ){

    if(depath==len){
        res.add(new ArrayList<>(path));//递归结束条件,就是当depath和数组一样,说明找到一组排列
        return;
    }

    // 在非叶子结点处,产生不同的分支,这一操作的语义是:在还未选择的数中依次选择一个元素作为下一个位置的元素,这显然得通过一个循环实现。
    for(int i=0;i<len;i++){
        if(!used[i]){//未被选择
        path.add(nums[i]);//往栈path里添加未选择的元素
        used[i]=true;//并且将标记为选择过
        

       //找一个等价表达式,就是要向下一层移动depath+1
       dfs(nums,len,depath+1,path,used,res);
        // 注意:下面这两行代码发生 「回溯」,回溯发生在从 深层结点 回到 浅层结点 的过程,代码在形式上和递归之前是对称的
         //上面递归结束,就回溯到上一次
         used[i]=false;
         path.remove(path.size()-1);

    }
}
    }
}