回溯
46.全排列
难度:⭐️⭐️⭐️⭐️
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
解法一 回溯
定义递归函数 backtrack(first,output) 表示从左往右填到第 first 个位置,当前排列为 output。first左边是已经填充的数,右边是未填充的。小技巧,用元素交换的方式来做标记数组。
回溯
private List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
List<Integer> output = new ArrayList<>();
for (int num : nums) {
output.add(num);
}
backtrace(nums.length, output, 0);
return ans;
}
private void backtrace(int n, List<Integer> output, int first) {
if (first == n) {
ans.add(new ArrayList<>(output));
}
for (int i = first; i < n; i++) {
Collections.swap(output, first, i);
backtrace(n, output, first + 1);
Collections.swap(output, first, i);
}
}
51.N皇后
难度:⭐️⭐️⭐️⭐️
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n
皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n
皇后问题 的解决方案。
每一种解法包含一个不同的 n
皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例 1:
输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
解法一 回溯法 + 数组
要求行、列、同一斜线上不可以有棋子,用3个数组columns、diagonals0、diagonals1分别表示列中、主对角线方向、副对角线方向是否有值。 按行扫描,从第0行开始,尝试在0~n的位置放置棋子,当3个数组都不包含当前位置时,说明该位置是可以放置棋子的,放置棋子,并进入下一行。
数组
class Solution {
private List<List<String>> ans = new LinkedList<>();
public List<List<String>> solveNQueens(int n) {
Set<Integer> columns = new HashSet<>();
Set<Integer> diagonals0 = new HashSet<>();
Set<Integer> diagonals1 = new HashSet<>();
int[] queues = new int[n];
Arrays.fill(queues, -1);
solve(queues, 0, columns, diagonals0, diagonals1);
return ans;
}
private void solve(int[] queues, int index, Set<Integer> columns, Set<Integer> diagonals0, Set<Integer> diagonals1) {
int n = queues.length;
if (index == n) {
List<String> matrix = new LinkedList<>();
for (int i = 0; i < n; i++) {
char[] line = new char[n];
Arrays.fill(line, '.');
line[queues[i]] = 'Q';
matrix.add(new String(line));
}
ans.add(matrix);
} else {
for (int i = 0; i < n; i++) {
if (columns.contains(i)) {
continue;
}
if (diagonals0.contains(index - i)) {
continue;
}
if (diagonals1.contains(index + i)) {
continue;
}
queues[index] = i;
columns.add(i);
diagonals0.add(index - i);
diagonals1.add(index + i);
solve(queues, index + 1, columns, diagonals0, diagonals1);
queues[index] = -1;
columns.remove(i);
diagonals0.remove(index - i);
diagonals1.remove(index + i);
}
}
}
}
3个数组的空间复杂度较高,也可以使用int值结合位运算来保存列和对角线上是否出现的元素。
78.子集
难度:⭐️⭐️⭐️⭐️
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
解法一 回溯
构建dfs(cur,n) 参数表示当前位置是 cur,原序列总长度为 n,cur位置的元素可以选中或不选中,根据该条件递归。
回溯
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> output = new LinkedList<>();
backtrace(ans, output, nums, 0);
return ans;
}
private void backtrace(List<List<Integer>> ans, List<Integer> output, int[] nums, int index) {
if (index == nums.length) {
ans.add(new ArrayList<>(output));
} else {
output.add(nums[index]);
backtrace(ans, output, nums, index + 1);
output.remove(output.size() - 1);
backtrace(ans, output, nums, index + 1);
}
}
解法二 位运算
长度为n的数组,一共可以组成2^n个子数组,对于每一位数字,比特位是1认为使用该元素,0认为不使用。
79.单词搜索
难度:⭐️⭐️⭐️
给定一个 m x n
二维字符网格 board
和一个字符串单词 word
。如果 word
存在于网格中,返回 true
;否则,返回 false
。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" 输出:true
解法一 回溯
类似图的遍历,找到匹配的首字母后,向上下左右4个方向搜索,用额外的数组记录当前位置是否被搜索过,然后递归调用。
回溯
private boolean[][] visited;
public boolean exist(char[][] board, String word) {
int m = board.length;
int n = board[0].length;
visited = new boolean[m][n];
char c = word.charAt(0);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == c) {
boolean found = find(board, i, j, word, 0);
if (found) {
return true;
}
}
}
}
return false;
}
private boolean find(char[][] board, int x, int y, String word, int index) {
char c = word.charAt(index);
if (board[x][y] != c) {
return false;
}
if (index == word.length() - 1) {
return true;
} else {
visited[x][y] = true;
// left
if (0 < y && !visited[x][y - 1]) {
boolean found = find(board, x, y - 1, word, index + 1);
if (found) {
return true;
}
}
// top
if (0 < x && !visited[x - 1][y]) {
boolean found = find(board, x - 1, y, word, index + 1);
if (found) {
return true;
}
}
// right
if (y + 1 < board[0].length && !visited[x][y + 1]) {
boolean found = find(board, x, y + 1, word, index + 1);
if (found) {
return true;
}
}
// bottom
if (x + 1 < board.length && !visited[x + 1][y]) {
boolean found = find(board, x + 1, y, word, index + 1);
if (found) {
return true;
}
}
visited[x][y] = false;
return false;
}
}
131. 分割回文串
难度:⭐️⭐️⭐️
给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
解法一 回溯 + 动态规划
当搜索到第i位置时,s[0, i-1]位置的字符串已经被分成了回文串了,只需要找到下一个位置j,使得s[i, j]也是回文串。回文串用双指针时会有重复计算,使用动态规划,状态转移方程 fn(i, j) = fn(i + 1, j - 1) && s[i] == s[j]。i >= j时,子串长度 <= 1,固定位true。