回溯
- 组合问题
 - 组合总和
 - 全排列
 - 子集
 - 分割回文串
 - N皇后
 - 电话号码的字母组合
 - 单词搜索
 - 括号生成
 
组合问题
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
 示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
树形结构:
 
 代码:
 使用startIndex控制遍历,每一次递归相当于树形图中的下一层,startIndex相当于递归的深度
class Solution {
    private List<Integer> path =  new ArrayList<>();
    private List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        backTracing(n, k, 1);
        return res;
    }
    public void backTracing(int n, int k, int start) {
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        // 剪枝,循环的遍历:至多从 n - (k - path.size) + 1 的位置开始搜索
        for (int i=start; i <= n - (k - path.size()) + 1; i++) {
            path.add(i);
            backTracing(n, k, i + 1);  //  i + 1 是因为组合中不能有重复
            path.removeLast();
        }
    }
}
 
组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
 candidates 中的数字可以无限制重复被选取。
 说明:
 所有数字(包括 target)都是正整数。
 解集不能包含重复的组合。
 示例 1:
 输入:candidates = [2,3,6,7], target = 7,
 所求解集为: [ [7], [2,2,3] ]
树形结构
 
 代码
 因为该题目中,同一个数字可以无限制重复被选取,所以在递归函数调用时与上一题不同,i不需要+1
class Solution {
    private List<Integer> path = new ArrayList<>();
    private List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backtracing(candidates, target, 0, 0);
        return res;
    }
    public void backtracing(int[] candidates, int target, int sum, int start) {
        if (sum > target) return;
        if (sum == target) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i < candidates.length; i++) {
            path.add(candidates[i]);
            backtracing(candidates, target, sum + candidates[i], i);
            path.removeLast();  // 回溯,移除路径 path 最后一个元素
        }
    }
}
 
全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
 示例:
 输入: [1,2,3]
 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
树形结构
 
 代码
 排列问题,for循环i从0开始;组合问题是从startIndex开始;使用used数组标注该元素是否使用过
class Solution {
    private List<Integer> path = new ArrayList<>();
    private List<List<Integer>> res = new ArrayList<>();
    private boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];
        backTracing(nums);
        return res;
    }
    public void backTracing(int[] nums) {
        if (path.size() == nums.length) {
            res.add(new ArrayList<>(path));
            return;
        }
        // 排列问题,for循环i从0开始;组合问题是从startIndex开始
        for(int i = 0; i < nums.length; i++) {
            // 如果取过了就跳过
            if (used[i]) {
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            backTracing(nums);
            used[i] = false;
            path.removeLast();
        }
    }
}
 
子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
 说明:解集不能包含重复的子集。
 示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
树形结构
 
 代码
 因为是子集,只要递归到就可以直接加入结果。之前都是叶节点加入结果集,这一题在递归函数一开始就可以加入结果集。
class Solution {
    private List<Integer> path = new ArrayList<>();
    private List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backTracing(nums, 0);
        return res;
    }
    public void backTracing(int[] nums, int startIndex) {
        res.add(new ArrayList<>(path));
        if (path.size() == nums.length) {
            return;
        }
        for (int i = startIndex; i < nums.length; i++) {
            path.add(nums[i]);
            backTracing(nums, i + 1);
            path.removeLast(); 
        }
    }
}
 
分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
 返回 s 所有可能的分割方案。
 示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]
树形结构
 
 代码
 使用startIndex控制分割位置,[startIndex, i] 就是要截取的子串。
 判断这个子串是不是回文,如果是回文,就加入在path中,path用来记录切割过的回文子串。
class Solution {
    List<List<String>> res = new ArrayList<>();
    List<String> path = new ArrayList<>();
    public List<List<String>> partition(String s) {
        backTracing(s, 0);
        return res;
    }
    public void backTracing(String s, int startIndex) {
        if (startIndex == s.length()) {
            res.add(new ArrayList<>(path));
            return;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = startIndex; i < s.length(); i++) {
            sb.append(s.charAt(i));
            if (check(sb)) {
                path.add(sb.toString());
                backTracing(s, i + 1);
                path.removeLast();
            }
        }
    }
    public boolean check(StringBuilder sb) {
        for (int i = 0; i < sb.length() / 2; i++) {
            if (sb.charAt(i) != sb.charAt(sb.length() - 1 - i)) {
                return false;
            }
        }
        return true;
    }
}
 
N皇后
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
 
 树形结构
 
 代码
class Solution {
    List<List<String>> res = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        char[][] board = new char[n][n];
        for (char[] c : board) {
            Arrays.fill(c, '.');
        }
        backtracing(n, 0, board);
        return res;
    }
    public void backtracing(int n, int row, char[][] board) {
        if (row == n) {  // 终止条件
            res.add(array2List(board));
            return;
        }
        for (int col = 0; col < n; col++) {
            if (isValid(row, col, n, board)) {
                board[row][col] = 'Q';
                backtracing(n, row + 1, board);
                board[row][col] = '.';
            }
        }
    }
    public boolean isValid(int row, int col, int n, char[][] board) {
        // 判断列
        for (int i = 0; i < row; i++) {
            if (board[i][col] == 'Q') {
                return false;
            }
        }
        // 判断45° 斜线
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] == 'Q') {
                return false;
            }
        }
        // 判断 135 ° 斜线, 注意 j <= n - 1
        for (int i = row - 1, j = col + 1; i >= 0 && j <= n - 1; i--, j++) {
            if (board[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }
    public List<String> array2List(char[][] board) {
        List<String> list = new ArrayList<>();
        for (char[] c : board) {
            list.add(String.copyValueOf(c));
        }
        return list;
    }
}
 
电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
 
 示例 1:
 输入:digits = “23”
 输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
树形结构
 例如:输入:“23”,抽象为树形结构,如图所示:
 
 代码
class Solution {
    private List<String> res = new ArrayList<>();
    private String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    private StringBuilder temp = new StringBuilder();  // 相当于path
    public List<String> letterCombinations(String digits) {
        if (digits == null || digits.length() == 0) {
            return res;
        }
        backtracing(digits, 0);
        return res;
    }
    public void backtracing(String digits, int index) {
        if (index == digits.length()) {  // 叶节点的位置终止
            res.add(temp.toString());  // StringBuilder 转成String类型
            return;
        }
        String str = numString[digits.charAt(index) - '0'];
        for(int i=0; i < str.length(); i++) {  
            temp.append(str.charAt(i));
            backtracing(digits, index + 1);
            temp.deleteCharAt(temp.length() - 1);
        }
    }
}
 
单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
代码
class Solution {
    private int[][] directs = new int[][]{{-1,0}, {1,0}, {0,1}, {0,-1}};  // 定义四个方向
    private int m;  // board 的行数
    private int n;  // borad 的列数
    private char[] ws;  // word的字符数组
    private boolean[][] used; // board中的某元素是否使用过
    private char[][] boad;  
 
    public boolean exist(char[][] board, String word) {
        this.m = board.length;
        this.n = board[0].length;
        this.ws = word.toCharArray();
        this.used = new boolean[m][n];
        this.boad = board;
        for(boolean[] u : used) {
            Arrays.fill(u, false);
        }
        for (int i=0; i<m; i++) {
            for (int j=0; j < n; j++) {
                if (boad[i][j] == ws[0]) {
                    used[i][j] = true;
                    if (backtracing(i, j, 1)) {
                        return true;
                    } else {
                        used[i][j] = false;
                    }
                }
            }
        }
        return false;
    }
    public boolean backtracing(int i, int j, int index) {
        if (index == ws.length) {
            return true;
        }
        for (int[] direct : directs) {
            int newX = i + direct[0];
            int newY = j + direct[1];
            // 超出边界
            if (!isInArea(newX, newY)) {
                continue;
            }
            // 被使用过
            if (used[newX][newY]) {
                continue;
            }
            // 不是要找的值
            if (boad[newX][newY] != ws[index]) {
                continue;
            }
            // 开始递归
            used[newX][newY] = true;
            if (backtracing(newX, newY, index + 1)) { // 下一层递归成功
                return true;
            } else {
                used[newX][newY] = false;
            }
        }
        return false;
    }
    public boolean isInArea(int i, int j) {
        if (i >= 0 && i < m && j >= 0 && j < n) {
            return true;
        }
        return false;
    }
}
 
括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。
 示例 1:
 输入:n = 3
 输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
 示例 2:
 输入:n = 1
 输出:[“()”]
代码
 这题不采用和之前一样的代码结构,采用选与不选的方法
class Solution {
    private int n;
    private char[] path ;
    private List<String> ans = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        this.n = n;
        path = new char[n * 2];
        dfs(0, 0);
        return ans;    
    }
    // 用选与不选的思路进行递归
    // i 是第 i 个位置, open是左括号的数量
    private void dfs(int i, int open) {
        if (i == 2 * n) {
            ans.add(new String(path));
            return;
        }
        // 左括号的数量<n ,说明还能继续添加左括号
        if (open < n) {
            path[i] = '(';
            dfs(i + 1, open + 1);  // 下次递归
        }
        // 位置 i 时,右括号的位置 < 左括号的位置,说明还能继续添加右括号(如果右括号>左括号就不合法了)
        if (i - open < open) {
            path[i] = ')';
            dfs(i + 1, open);
        }
    }
}
                

















