17. 电话号码的字母组合
class Solution {
private Map<Integer,String> map = new HashMap<>();
List<String> list = new ArrayList<>();
//1.回溯算法
public List<String> letterCombinations(String digits) {
if(digits == null || digits.isEmpty()){
return list;
}
map.put(2,"abc");
map.put(3,"def");
map.put(4,"ghi");
map.put(5,"jkl");
map.put(6,"mno");
map.put(7,"pqrs");
map.put(8,"tuv");
map.put(9,"wxyz");
backtract(new StringBuilder(),digits.toCharArray(),0);
return list;
}
public void backtract(StringBuilder sb,char[] digits,int index){
if(index == digits.length){
list.add(sb.toString());
return;
}
String item = map.get(Integer.parseInt(String.valueOf(digits[index])));
for(char a : item.toCharArray()){
sb.append(a);
//回溯
backtract(sb,digits,index + 1);
//撤销选择
sb.deleteCharAt(sb.length() - 1);
}
}
}
22. 括号生成
class Solution {
private List<String> result = new ArrayList<>();
//标准的回溯算法,注意一定要先添加左括号,再添加右括号
public List<String> generateParenthesis(int n) {
backtract(n,0,0,"");
return result;
}
//一定要先添加左括号,右括号的个数必须⼩小于左括号
//如果我们还剩⼀一个位置,我们可以开始放一个左括号。 如果它不不超过左括号的数量量,我们可以放一个右括号。
public void backtract(int n,int open,int close,String s){
if(s.length() == n * 2){
result.add(s);
return;
}
if(open < n){
backtract(n,open + 1,close,s + "(");
}
if(close < open){
backtract(n,open,close + 1, s+")");
}
}
}
37. 解数独
思路:
数独特点:9 行 9 列
1.从第一个位置 (0,0) 开始判断,直到判断到第 (8,8) ,将合适的元素放在表格中。
2.用 count 来记录已经填充的数字,0 - 81。如果当前为位置有数字,则将 count + 1,继续递归判断下一个元素,根据count来计算当前在二维矩阵中的位置。
3.判断当前位置是否满足条件,即同一行是否出现重复,同一列是否出现重复,当前9宫格是否出现重复。如果都满足,将该数字放入指定位置。如果当前数字不满足,继续尝试 1 − 9 1-9 1−9 的下一个元素。直到满足条件。将 b o a r d [ r o w ] [ c o l ] = c board[row][col] = c board[row][col]=c 置位 c c c 。
4.继续递归下一个位置,此时将 + + c o u n t ++count ++count 传入。满足则继续递归。不满足则 回退,这也是回溯算法的原理所在:将 c o u n t − − count-- count−− 并且将 b o a r d [ r o w ] [ c o l ] = ′ . ′ board[row][col] = '.' board[row][col]=′.′
class Solution {
//解数独,回溯算法放元素,9行9列,
public void solveSudoku(char[][] board) {
solve(board,0);
}
/**
* count 表示已经走到的总位置
*/
public boolean solve(char[][] board,int count){
if(count == 81) return true;
//1.根据 count 计算当前所在的位置
int row = count / 9;
int col = count % 9;
//1.如果当前位置已经有数据了,则继续下一个位置
if(board[row][col] != '.'){
if(solve(board,++count)) return true;
return false;
}
//如果当前位置是 "."
for(char c = '1'; c <= '9'; c++){
if(!isValidePosition(board,row,col,c)) continue;
//满足,能放
board[row][col] = c;
//继续尝试下一个位置
if(solve(board,++count)) return true;
//否则回溯
count--;
board[row][col] = '.';
}
return false;
}
/**
* row 横坐标
* col 纵坐标
* c 当前要放置的元素
*/
public boolean isValidePosition(char[][] board,int row,int col,char c){
//1.先判断当前行
for(int i = 0; i < 9; i++){
if(board[i][col] == c) return false;
}
//2.判断当前列是否满足
for(int i = 0; i < 9; i++){
if(board[row][i] == c) return false;
}
//3.判断当前9宫格是否满足条件,根据row和col计算当前位置位于哪个格子,拿到格子的起始位置
int rBegin = (row / 3) * 3;
int cBegin = (col / 3) * 3;
for(int i = 0 ; i < 3; i++){
for(int j = 0;j < 3;j++){
if(board[rBegin + i][cBegin + j] == c) return false;
}
}
return true;
}
}
39. 组合总和
class Solution {
/**
* 标准的回溯算法
* 1.既然是求和,那肯定要先将 candidates 进行排序,方便取值及判断。
* 2.题目要求数据可以重复取值,所以在递归的时候,需要注意传入的下一个条件是否需要+1
* 3.每取一个值,就将 target -= a,传入到一下次递归中,
* 4.剪枝操作,当 taget < nums[i] 的时候,此时再去取值已经不满足条件了,毕竟一个排序过了。直接 break
*/
private List<List<Integer>> result = new ArrayList();
private int[] candidates;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
this.candidates = candidates;
backtrack(target,0,new ArrayList<>());
return result;
}
public void backtrack(int target,int index,List<Integer> item){
if(target == 0){
result.add(new ArrayList<>(item));
return;
}
for(int i = index; i < candidates.length;i++){
//注意,如果当前 target 的值 比 candidates[i] 还小,那 target - candidates[i] < 0 不满足,后面的元素就更不满足了,已经对数组进行排序了
if(target < candidates[i]) break;
//选择
item.add(candidates[i]);
//回溯递归,注意,因为本题目允许选择重复的元素,所以此时递归传入的参数还是 index 而不是 index + 1;
backtrack(target- candidates[i],i,item);
//回溯,撤销选择
item.remove(item.size() - 1);
}
}
}
40. 组合总和 II
注意:
与39题不同的是,组合中不能出现重复的元素。
例如: 1,1,2,3 就可能出现 1,2,3 和 第二个 1 组成的 1,2,3
在回溯算法的剪枝操作中:
- 如果当前 target 比当前加入的元素还要小,那就直接 break了,因为越往后数越大
- 如果当前数字和前一个数字相等,应当进行剪枝去重。
class Solution {
private List<List<Integer>> result = new ArrayList();
private int[] candidates;
/**
* 与39题不同的是不允许使用重复的元素
* 1.数组中存在重复的元素,所有 1,1,2,3 这种就会出现 1,2,3 和 1,2,3 两次,需要做一次剪枝去重。
*/
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
this.candidates = candidates;
backtrack(target,0,new ArrayList<>());
return result;
}
//数组中的每个元素只能出现一次
public void backtrack(int target,int index,List<Integer> item){
if(target == 0){
result.add(new ArrayList<>(item));
return;
}
for(int i = index; i < candidates.length; i++){
if(target < candidates[i]) break;
//对结果进行剪枝,比如两个 1,1 都为第一个
if(i > index && candidates[i - 1] == candidates[i]) continue;
item.add(candidates[i]);
backtrack(target - candidates[i],i + 1,item);
//回溯
item.remove(item.size() - 1);
}
}
}
46. 全排列
方法一:
全排列问题:
- 数组里每个元素都要取到,所以用一个数组标记为来判断当前元素是否已取
- 在回溯之后进行状态恢复,将该元素标记为未使用,并item中的元素移除
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private int[] nums;
//全排列问题,就是添加一个标记为的问题
public List<List<Integer>> permute(int[] nums) {
this.nums = nums;
backtrack(new ArrayList<>(),new boolean[nums.length]);
return result;
}
public void backtrack(List<Integer> item,boolean[] flag){
if(item.size() == nums.length){
result.add(new ArrayList<>(item));
return;
}
for(int i = 0 ; i < nums.length; i++){
if(!flag[i]){
item.add(nums[i]);
flag[i] = true;
backtrack(item,flag);
//状态回溯
flag[i] = false;
item.remove(item.size() - 1);
}
}
}
}
方法二:通过交换数组中的元素位置,知道交换到最后一个位置
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private int[] nums;
public List<List<Integer>> permute(int[] nums) {
this.nums = nums;
List<Integer> list = new ArrayList<>();
for(int i = 0; i < nums.length; i++){
list.add(nums[i]);
}
backtrack(list,0);
return result;
}
public void backtrack(List<Integer> item,int first){
if(first == nums.length){
result.add(new ArrayList<>(item));
return;
}
for(int i = first; i < nums.length; i++){
Collections.swap(item,first,i);
backtrack(item,first + 1);
Collections.swap(item,first,i);
}
}
}
47. 全排列 II
如果存在重复的数字 ,我们每次会将重复的数字都重新填上去并继续尝试导致最后答案的重复,因此我们需要处理这个情况。
因为每次假设我们添加的第一个元素为1,后续会添加1,2。但是当将第二个1作为第一位时候,从数组冲查找未使用过的数字时候,又会再次出现1,1,2,导致重复
class Solution {
private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
backtrack(nums,new ArrayList<>(),new boolean[nums.length]);
return result;
}
public void backtrack(int[] nums,List<Integer> item,boolean[] flag){
if(item.size() == nums.length){
result.add(new ArrayList<>(item));
return;
}
for(int i = 0; i < nums.length; i++){
if(flag[i]) continue;
// 对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字
if(i > 0 && nums[i] == nums[i - 1] && flag[i - 1]) continue;
item.add(nums[i]);
flag[i] = true;
backtrack(nums,item,flag);
flag[i] = false;
item.remove(item.size() - 1);
}
}
}
51. N 皇后
对角线特点:
- 从左上到右下:x - y 为一个固定的值。
- 从右上到左下:x + y 为一个固定的值。
这样就可以用一个集合来保存判断皇后咋在每个对角线的位置上的标致。
class Solution {
//返回的结果
private List<List<String>> result = new ArrayList<>();
//保存 0 - n 列中皇后的位置
private Set<Integer> cols = new HashSet<>();
//保存对角线 皇后的位置索引,注意,同一个对角线特点:x - y 为一个固定的值:比如 (0,1),(1,2),(2,3)
private Set<Integer> diagonal1 = new HashSet<>();
//保存些角线皇后位置,该方向上 x + y 的值为一个固定的值。(0,3),(1,2),(2,1)
private Set<Integer> diagonal2 = new HashSet<>();
public List<List<String>> solveNQueens(int n) {
int[] queue = new int[n];
Arrays.fill(queue,-1);
backtrack(queue,n,0);
return result;
}
/**
* 每次遍地到最后一行可以得到一个答案
*/
public void backtrack(int[] queue,int n,int row){
if(row == n){
List<String> item = buildList(queue,n);
result.add(item);
return;
}
//一行一行的找位置放置皇后
for(int i = 0; i < n; i++){
//1.如果当前列已经存在皇后了,则不再继续往下一行找了,肯定不满足
if(cols.contains(i)) continue;
//2.判断当前正斜线是否满足
int diagonalNum1 = i - row;
if(diagonal1.contains(diagonalNum1)) continue;
//3.判断反斜线是否满足
int diagonalNum2 = i + row;
if(diagonal2.contains(diagonalNum2)) continue;
//4.都满足了,该位置可以放置皇后,即第 row 行的第 i 个位置为皇后
queue[row] = i;
cols.add(i);
diagonal1.add(diagonalNum1);
diagonal2.add(diagonalNum2);
//回溯
backtrack(queue,n,row + 1);
//状态重置
queue[row] = -1;
cols.remove(i);
diagonal1.remove(diagonalNum1);
diagonal2.remove(diagonalNum2);
}
}
/**
* 根据传入的数组生成列表
* queue n 行每个皇后在的列数索引
*/
public List<String> buildList(int[] queue,int n){
List<String> ans = new ArrayList<>();
for(int i = 0; i < n; i++){
char[] item = new char[n];
Arrays.fill(item,'.');
//第 i 行 皇后的位置在第几列,在第 queue[i] 列
item[queue[i]] = 'Q';
ans.add(new String(item));
}
return ans;
}
}
77. 组合
常规回溯算法
class Solution {
private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtrack(new ArrayList<>(),k,n,1);
return result;
}
public void backtrack(List<Integer> item,int k,int n,int index){
if(item.size() == k){
result.add(new ArrayList<>(item));
return;
}
for(int i = index; i <= n;i++){
item.add(i);
backtrack(item,k,n,i + 1);
item.remove(item.size() - 1);
}
}
}
78. 子集
注意:
与求组合不同的是,递归结束的条件判断,当前 item 的数组长度都小于 n 时,这个 item 是符合条件的。将其添加到集合中。并且不能直接返回。
不然只能添加一个初始的空集合。
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private int[] nums;
public List<List<Integer>> subsets(int[] nums) {
this.nums = nums;
backtrack(new ArrayList<>(),0);
return result;
}
public void backtrack(List<Integer> item,int index){
if(index <= nums.length){
result.add(new ArrayList<>(item));
}
for(int i = index; i < nums.length; i++){
item.add(nums[i]);
backtrack(item,i + 1);
item.remove(item.size() - 1);
}
}
}
79. 单词搜索
class Solution {
private char[][] board;
private int row, col;
private String word;
public boolean exist(char[][] board, String word) {
this.row = board.length;
this.col = board[0].length;
this.board = board;
this.word = word;
for(int i = 0; i < row; i++){
for(int j = 0;j < col; j++){
if(backtrack(new boolean[row][col],i,j,0)) return true;
}
}
return false;
}
public boolean backtrack(boolean[][] flag,int x,int y,int count){
if(x < 0 || x >= row || y < 0 || y >= col || board[x][y] != word.charAt(count) || flag[x][y]) return false;
if(count == word.length() - 1) return true;
flag[x][y] = true;
boolean result = backtrack(flag,x - 1,y,count + 1) ||
backtrack(flag,x + 1,y,count + 1) ||
backtrack(flag,x,y + 1,count + 1) ||
backtrack(flag,x,y - 1,count + 1);
if(result){
return true;
}else{
flag[x][y] = false;
return false;
}
}
}