回溯
- 组合问题
- 组合总和
- 全排列
- 子集
- 分割回文串
- 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);
}
}
}