使用DFS实现
步骤

  1. 确定函数参数
  2. 确认终止条件
  3. 确定回溯条件

先修改temp 回溯后撤回
对具体情况进行剪枝

组合

  • 子集
  • 子集II
  • 组合
  • 组合总和
  • 组合总和II

全排列

  • 全排列
  • 全排列II
  • 字符串全排列
  • 字母大小写全排列

括号生成

  • 括号生成

搜索

  • 数独
  • N皇后
  • 分割字符串
  • 二进制手表

子集是完全回溯单点不返回
子集2是子集1+剪枝右子树
组合是子集+剪枝长度
组合总数是完全回溯+单点返回

组合

子集

  1. 输入:nums = [1,2,3]
  2. 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

image.png

class Solution {
    List<Integer> t = new ArrayList();
    List<List<Integer>> res = new ArrayList();

    public List<List<Integer>> subsets(int[] nums) {
        back(0, nums);
        return res;
    }   

    public void back(int cur, int[] nums) {
        if(cur == nums.length) {
            res.add(new ArrayList(t));
            return;
        }
        t.add(nums[cur]);
        back(cur + 1, nums);
        t.remove(t.size() - 1);
        back(cur+1, nums);
    }
}

//解法II  二进制枚举
//以001  即选择num[2]
class Solution {
    List<Integer> t = new ArrayList<Integer>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();

    public List<List<Integer>> subsets(int[] nums) {
        int n = nums.length;
        for (int mask = 0; mask < (1 << n); ++mask) {
            t.clear();
            for (int i = 0; i < n; ++i) {
                if ((mask & (1 << i)) != 0) {
                    t.add(nums[i]);
                }
            }
            ans.add(new ArrayList<Integer>(t));
        }
        return ans;
    }
}

子集II

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

在子集基础上进行剪枝操作(排序+右子树剪枝)

class Solution {
    List<Integer> t = new ArrayList();
    List<List<Integer>> res = new ArrayList();

    public List<List<Integer>> subsets(int[] nums) {
        Arrays.sort(nums);
        back(0, nums);
        return res;
    } 

    public void back(boolean x, int cur, int[] nums) {
        if(cur == nums.length) {
            res.add(new ArrayList(t));
            return;
        } 
        t.add(nums[cur]);
        back(true, cur + 1, nums);
        t.remove(t.size()-1);
        if(x && cur > 0 && nums[cur] == nums[cur-1]) return; 
        back(false, cur + 1, nums);    
    }
}

组合

输入:n = 4, k = 2
输出:
[ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4] ]

在子集基础上对长度进行剪枝
剪枝:temp 长度加上区间 [cur, n] 的长度小于 k,不可能构造出长度为 k 的 temp

class Solution {
    List<List<Integer>> res = new ArrayList();
    List<Integer> t = new ArrayList();
    public List<List<Integer>> combine(int n, int k) {
        back(1, n, k);
        return res;
    }
    public void back(int cur, int n, int k) {

        if(t.size() + (n - cur + 1) < k) return;
        if(t.size() == k){
            res.add(new ArrayList(t));
            return;
        }

        t.add(cur);
        back(cur+1, n, k);
        t.remove(t.size()-1);
        back(cur+1, n, k);
    }
}

组合总数

输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]

在子集基础上进行单节点回溯

class Solution {
   List<List<Integer>> res = new ArrayList();
   List<Integer> t = new ArrayList();
   public List<List<Integer>> combinationSum(int[] candidates, int target) {
        dfs(0, target, candidates);
        return res;
    }

    public void dfs(int cur, int target, int[] nums) {
        if (cur == nums.length) {
            return;
        }
        if (target == 0) {
            res.add(new ArrayList(t));
            return;
        }
        // 直接跳过
        dfs(cur+1, target, nums);
        // 选择当前数
        if (target - nums[cur] >= 0) {
            t.add(nums[cur]);
            dfs(cur, target - nums[cur], nums);
            t.remove(t.size() - 1);
        }
    }
}

组合总数II

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[ [1,2,2], [5] ]
class Solution {
    List<int[]> freq = new ArrayList<int[]>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> sequence = new ArrayList<Integer>();

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        for (int num : candidates) {
            int size = freq.size();
            if (freq.isEmpty() || num != freq.get(size - 1)[0]) {
                freq.add(new int[]{num, 1});
            } else {
                ++freq.get(size - 1)[1];
            }
        }
        dfs(0, target);
        return ans;
    }

    public void dfs(int pos, int rest) {
        if (rest == 0) {
            ans.add(new ArrayList<Integer>(sequence));
            return;
        }
        if (pos == freq.size() || rest < freq.get(pos)[0]) {
            return;
        }

        dfs(pos + 1, rest);

        int most = Math.min(rest / freq.get(pos)[0], freq.get(pos)[1]);
        for (int i = 1; i <= most; ++i) {
            sequence.add(freq.get(pos)[0]);
            dfs(pos + 1, rest - i * freq.get(pos)[0]);
        }
        for (int i = 1; i <= most; ++i) {
            sequence.remove(sequence.size() - 1);
        }
    }
}

括号生成

括号生成

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
class Soluition {

    List<String> res = new ArrayList();
    public List<String> generateParenthesis(int n) {
        back(new StringBuilder(), 0, 0, n);
        return res;
    }

    public void back(StringBuilder cur, int left, int righht, int n) {
        if(cur.length() == n << 1) {
            res.add(cur.tostring());
            return;
        }
        if(left < n) {
            cur.append('(');
            backl(cur, left+1, right, n);
            cur.deleteCharAt(cur.lengtgh()-1);
        }
        if(right < n) {
            cur.append(')');
            back(cur, left, righht+1, n);
            cur.deletChatAt(cur.length()-1);
        }
    }
}

全排列

全排列

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

全排列类似于一颗多叉树的遍历。你可以选择一个分支,遍历后撤回

image.png
解法I
image.png
解法II
回溯 - 图4


//1 使用标记数组
//  使用boolean 标记第几个在前  可实现字典序
class Solution {
    boolean[] is;
    int[] nums;
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> t = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        this.is = new boolean[nums.length];
        this.nums = nums;
        backtracking();
        return res;
    }
    void backtracking() {
        if(t.size() == nums.length) {
            res.add(new ArrayList(t));
            return;
        }
        for(int i = 0; i < nums.length; i++) {
            if(is[i]) {
                continue;
            }

            path.add(nums[i]);
            is[i] = true;
            backtracking();
            is[i] = false;
            path.remove(t.size() - 1);
        }
    }
}

//2 使用数组交换的方式进行全排列 
//  建立一个ArrayList添加元素依次调整123位
class Solution {
    private List<List<Integer>> res = new ArrayList();
    private List<Integer> t = new ArrayList();
    private int[] nums;
    public List<List<Integer>> permute(int[] nums) {
        this.nums = nums;
        for(int num : nums) {
            t.add(num);
        }
        back(0);
        return res;
    }
    public void back(int cur){
        if(cur == nums.length) {
            res.add(new ArrayList(t));
            return;
        }

        for(int i = cur; i < nums.length; i++) {
            Collections.swap(t, cur, i);
            back(cur + 1);
            Collections.swap(t, cur, i);
        }

    }
}

全排列II

输入:nums = [1,1,2]
输出:[[1,1,2], [1,2,1], [2,1,1]]

image.png
剪枝右子树

//可以同时解决全排列问题
//与全排列相比只增加一个(i > 0 && nums[i] == nums[i - 1] && !is[i - 1]) 剪枝右子树
class Solution {
    boolean[] is;
    int[] nums;
    List<List<Integer>> res =  new ArrayList<List<Integer>>();
    List<Integer> t = new ArrayList<Integer>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        this.is = new boolean[nums.length];
        Arrays.sort(nums);
        this.nums = nums;
        backtrack();
        return res;
    }

    public void backtrack() {
        if (t.size() == nums.length) {
            res.add(new ArrayList(t));
            return;
        }
        for (int i = 0; i < nums.length; ++i) {
            if (is[i] || (i > 0 && nums[i] == nums[i - 1] && !is[i - 1])) {
                continue;
            }
            t.add(nums[i]);
            is[i] = true;
            backtrack();
            is[i] = false;
            t.remove(t.size()-1);
        }
    }
}

//剪枝不完全 不能解决0019 因为  9010   i=1  i=3 而我们比较相邻的
class Solution {
    private List<List<Integer>> res = new ArrayList();
    private List<Integer> t = new ArrayList();
    private int[] nums;
    private boolean[] is;
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        this.nums = nums;
        for(int num : nums) {
            t.add(num);
        }
        is = new boolean[nums.length];
        back(0);
        return res;
    }
    public void back(int cur){
        if(cur == nums.length) {
            res.add(new ArrayList(t));
            return;
        }
        for(int i = cur; i < nums.length; i++) {
            if(i > 0 && t.get(i) == t.get(i-1) && !is[i-1]) {
                continue;
            }
            Collections.swap(t, cur, i);
            is[i] = true;
            back(cur + 1);
            is[i] = false;
            Collections.swap(t, cur, i);
        }

    }
}

字母大小写全排列

输入:S = "a1b2"
输出:["a1b2", "a1B2", "A1b2", "A1B2"]
class Solution {
    List<String> list = new ArrayList();
    public List<String> letterCasePermutation(String s) {
        getStr(0, s, new StringBuffer());
        return list;
    }

    public void getStr(int index, String s, StringBuffer sb){
        if(index == s.length()){
            list.add(sb.toString());
            return;
        }
        char ch = s.charAt(index);
        sb.append(ch);
        getStr(index + 1, s, sb);
        sb.deleteCharAt(sb.length() - 1);

        // 判断是否是字符,如果是,则转换大小写
        if(!Character.isDigit(ch)){
            ch = (char)(ch - 'a' >= 0 ? ch - 32 : ch + 32);
            sb.append(ch);
            getStr(index + 1, s, sb);
            sb.deleteCharAt(sb.length() - 1);
        }
    }
}

电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

image.png


class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> combinations = new ArrayList<String>();
        if (digits.length() == 0) {
            return combinations;
        }
        Map<Character, String> phoneMap = new HashMap<Character, String>() {{
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};
        backtrack(combinations, phoneMap, digits, 0, new StringBuffer());
        return combinations;

    }

    public void backtrack(List<String> combinations, Map<Character, String> phoneMap,String digits, int index, StringBuffer combination) {
          if(index == digits.length()) {
              combinations.add(combination.toString());
          } else{
            char digit = digits.charAt(index);
            String letters = phoneMap.get(digit);
            int lettersCount = letters.length();
            for (int i = 0; i < lettersCount; i++) {
                combination.append(letters.charAt(i));
                backtrack(combinations, phoneMap, digits, index + 1, combination);
                combination.deleteCharAt(index);
            }

        }
    }
}

字符串的排列

输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
class Solution {
    List<String> res = new LinkedList<>();
    char[] c;
    public String[] permutation(String s) {
        c = s.toArray();
        dfs(0);
        return c.toArray(new String(c.size()));
    }
    void dfs(int x) {
        if(x == c.length -1){
            res.add(String.valueOf(c));
            return;
        }
        HashSet<Character> set = new HashSet();
        for(int i = x; i< c.length; i++) {
            if(set.contains(c[i])) continue;
            set.add(c[i]);
            swap(i, x);
            dfs(x+1);
            swap(i, x);
        }
    }
    void swap(int a, int b) {
        char tmp = c[a];
        c[a] = c[b];
        c[b] = tmp;
    }

}

搜索

岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1
class Solution {
    public int numIslands(char[][] grid) {
      int count = 0;
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[0].length; j++) {
                if(grid[i][j] == '1'){
                    dfs(grid, i, j);
                    count++;
                }
            }
        }
        return count;
    }
    public void dfs(char[][] grid, int i, int j){
        if(i < 0|| j < 0 || i >= grid.length|| j >= grid[0].length || grid[i][j] == '0') return;
        grid[i][j] ='0';
        dfs(grid, i + 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i - 1, j);
        dfs(grid, i, j - 1);
    }

}

所有可能的路径

image.png

输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
class Solution {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    Deque<Integer> stack = new LinkedList();

    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        stack.addLast(0);
        dfs(graph, 0, graph.length - 1);
        return ans;
    }

    public void dfs(int[][] graph, int x, int n) {
        if (x == n) {
            ans.add(new ArrayList<Integer>(stack));
            return;
        }
        for (int y : graph[x]) {
            stack.addLast(y);
            dfs(graph, y, n);
            stack.removeLast();
        }
    }
}