目录
理论基础
通用
子集
组合
子集的基础上,长度判定+剪枝
排列
通用的基础上,加一个Boolean判定选过没
路径搜索
对访问过的网格单元会进行临时标记,并在回溯时恢复状态
17.电话号码
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
基础题
class Solution {
private static final String[] MAPPING = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
private final List<String> ans = new ArrayList<>();
private char[] digits;
private char[] path;
public List<String> letterCombinations(String digits) {
if ( digits.length()== 0) return List.of();//空字符串,返回一个空列表结果
this.digits = digits.toCharArray();
path = new char[digits.length()]; //长度固定
dfs(0);
return ans;
}
private void dfs(int i) {
if (i == digits.length) {
ans.add(new String(path));
return;
}
for (char c : MAPPING[digits[i] - '0'].toCharArray()) {//遍历当前数字对应的字母组合中的每一个字母
//digits[i] - '0',字符 '2' 变为整数 2,用作索引查找 MAPPING 数组中的对应项
//.toCharArray()"abc"转换['a', 'b', 'c']
path[i] = c; // 直接覆盖
dfs(i + 1);
}
}
}
78.子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的
子集
(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums 中的所有元素 互不相同
class Solution {
private final List<List<Integer>> ans = new ArrayList<>();
private final List<Integer> path = new ArrayList<>();
private int[] nums;
public List<List<Integer>> subsets(int[] nums) {
this.nums = nums;
dfs(0);
return ans;
}
private void dfs(int i) {
ans.add(new ArrayList<>(path));
for (int j = i; j < nums.length; j++) {
path.add(nums[j]);
dfs(j + 1);
path.remove(path.size() - 1);
}
}
}
131.分割回文串
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是
回文串
。返回 s 所有可能的分割方案。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
提示:
1 <= s.length <= 16
s 仅由小写英文字母组成
子集题
class Solution {
private final List<List<String>> ans = new ArrayList<>();
private final List<String> path = new ArrayList<>();
private String s;
public List<List<String>> partition(String s) {
this.s = s;
dfs(0);
return ans;
}
private void dfs(int i) {
if (i == s.length()) {
ans.add(new ArrayList<>(path)); // 复制 path
return;
}
for (int j = i; j < s.length(); j++) { // 枚举子串的结束位置
if (isPalindrome(i, j)) {
path.add(s.substring(i, j + 1));//s.substring(i, j + 1):从字符串 s 中获取从索引 i 到 j(包含 j)的子串
dfs(j + 1);
path.remove(path.size() - 1); //移除列表的末尾 //每次偶要回溯
}
}
}
private boolean isPalindrome(int left, int right) {
while (left < right) {
if (s.charAt(left++) != s.charAt(right--)) {
return false;
}
}
return true;
}
}
39.组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1
输出: []
提示:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
candidates 的所有元素 互不相同
1 <= target <= 40
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
dfs(0, target, candidates, ans, path);
return ans;
}
private void dfs(int i, int left, int[] candidates, List<List<Integer>> ans, List<Integer> path) {
//left 是目标和 target 减去当前组合中的数之后剩余的差值
if (left == 0) {
ans.add(new ArrayList<>(path));
return;
}
if (left < candidates[i]) return;
//减去则会变成负数
//已经排序,后续的候选数只会更大
for (int j = i; j < candidates.length; j++) {
path.add(candidates[j]);
dfs(j, left - candidates[j], candidates, ans, path);
path.remove(path.size() - 1); // 恢复现场
}
}
}
22.括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1
输出:["()"]
提示:
1 <= n <= 8
class Solution {
private int n;
private final List<Integer> path = new ArrayList<>();
private final List<String> ans = new ArrayList<>();
public List<String> generateParenthesis(int n) {
this.n = n;
dfs(0, 0);
return ans;
}
private void dfs(int i, int b) {
if (path.size() == n) {//左括号满,生成s存入
char[] s = new char[n * 2];
Arrays.fill(s, ')');
for (int j : path) s[j] = '(';
ans.add(new String(s));
return;
}
for (int close = 0; close <= b; close++) { //填 close填充的右括号的个数
path.add(i + close); //位置 i + close 处放置一个左括号
dfs(i + close + 1, b - close + 1);//下一个位置是 i + close + 1 //更新括号的平衡状态
path.remove(path.size() - 1);
}
}
}
46.全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums 中的所有整数 互不相同
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = Arrays.asList(new Integer[nums.length]);
boolean[] is = new boolean[nums.length]; // 标记每个数字是否已经选过
dfs(0, nums, ans, path, is);
return ans;
}
private void dfs(int i, int[] nums, List<List<Integer>> ans, List<Integer> path, boolean[] is) {
if (i == nums.length) {
ans.add(new ArrayList<>(path));
return;
}
for (int j = 0; j < nums.length; j++) {
if (!is[j]) {
path.set(i, nums[j]); // 将第 i 个位置设置为 nums[j]
is[j] = true;
dfs(i + 1, nums, ans, path, is);
is[j] = false; // 恢复现场,将 nums[j] 设为未选状态
// path长度在初始化时固定为 nums.length,因此不需要在每次递归时进行恢复操作,直接覆盖即可
}
}
}
}
51.N皇后h
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
提示:
1 <= n <= 9
说人话:不能同行同列同斜线
多了行列问题+打印棋盘的步骤
class Solution {
public List<List<String>> solveNQueens(int n) {
List<List<String>> ans = new ArrayList<>();
int[] col = new int[n]; // 行列对应,例如 col[r] = c 表示第 r 行的皇后在第 c 列
boolean[] onPath = new boolean[n]; // 标记每列是否已有皇后
boolean[] diag1 = new boolean[n * 2 - 1]; // 标记主对角线(左上到右下)是否已有皇后,主对角线编号为 r + c
boolean[] diag2 = new boolean[n * 2 - 1]; // 标记副对角线(右上到左下)是否已有皇后,副对角线编号为 r - c + n - 1
dfs(0, n, col, onPath, diag1, diag2, ans); // 从第 0 行开始放置皇后
return ans;
}
private void dfs(int r, int n, int[] col, boolean[] onPath, boolean[] diag1, boolean[] diag2, List<List<String>> ans) {
if (r == n) { // 当 r == n 时,表示已经成功在所有行放置了皇后
List<String> board = new ArrayList<>(n);
for (int c : col) { // 根据 col 数组构建棋盘
char[] row = new char[n];
Arrays.fill(row, '.');
row[c] = 'Q';
board.add(new String(row));
}
ans.add(board);
return;
}
for (int c = 0; c < n; c++) { // 尝试在当前行 r 的每一列 c 放置皇后
int rc = r - c + n - 1; // 计算当前列 c 对应的副对角线编号
if (!onPath[c] && !diag1[r + c] && !diag2[rc]) { // 如果当前列、主对角线、副对角线都没有皇后
col[r] = c; // 记录当前行 r 的皇后放在第 c 列
onPath[c] = diag1[r + c] = diag2[rc] = true;
dfs(r + 1, n, col, onPath, diag1, diag2, ans);
onPath[c] = diag1[r + c] = diag2[rc] = false;
}
}
}
}
79.单词
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
提示:
m == board.length
n = board[i].length
1 <= m, n <= 6
1 <= word.length <= 15
board 和 word 仅由大小写英文字母组成
路径搜索问题
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (dfs(board, words, i, j, 0)) return true;//从每行第一个开始遍历
}
}
return false;
}
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k])
return false;// 越界
if (k == word.length - 1) //匹配到了单词的最后一个字符,返回 true
return true;
board[i][j] = '\0';//标记
boolean res = dfs(board, word, i + 1, j, k + 1) || // 向下走
dfs(board, word, i - 1, j, k + 1) || // 向上走
dfs(board, word, i, j + 1, k + 1) || // 向右走
dfs(board, word, i, j - 1, k + 1); // 向左走
board[i][j] = word[k];// 恢复标记
return res;
}
}