回溯
- 回溯是一种通过穷举所有可能情况来找到所有解的算法。如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。回溯算法一般会结合在搜索算法中。
电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
class Solution {
//需要一个map去初始化键盘数据
private Map<Integer, String> info;
public List<String> letterCombinations(String digits) {
List<String> ret = new ArrayList<>();
if (digits.length() == 0) {
return ret;
}
//初始化键盘
info = new HashMap<>();
info.put(2, "abc");
info.put(3, "def");
info.put(4, "ghi");
info.put(5, "jkl");
info.put(6, "mno");
info.put(7, "pqrs");
info.put(8, "tuv");
info.put(9, "wxyz");
//使用深度优先加回溯思想完成
DFS(digits, ret, "", 0);
return ret;
}
private void DFS(String digits, List<String> ret, String curStr, int curLength) {
//如果当前的字符串与输入数字的长度相同 说明一个组合完成
if (curLength == digits.length()) {
ret.add(curStr);
return;
}
//获取当前的键盘数字输入
int num = digits.charAt(curLength) - '0';
String chars = info.get(num);
for (int i = 0; i < chars.length(); i++) {
curStr += chars.charAt(i);
DFS(digits, ret, curStr, curLength + 1);
//回溯
curStr = curStr.substring(0, curStr.length() - 1);
}
}
}
组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
class Solution {
private List<List<Integer>> ret;
private void DFS(int[] candidates, List<Integer> list, int cur, int target, int prevPose) {
if (cur >= target) {
if (cur == target) {
//需要将链表中的值拷贝一份 不然引用的是同一个变量
List<Integer> curList = new ArrayList<>(list);
ret.add(curList);
}
return;
}
//遍历累加数字可以重复取 而且避免重复组合 直接从上次位置开始累加
for (int i = prevPose; i < candidates.length; i++) {
//将当前数字加到链表中
list.add(candidates[i]);
//进行递归处理
DFS(candidates, list, cur + candidates[i], target, i);
//回溯
list.remove(list.size() - 1);
}
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//初始化返回结果
ret = new ArrayList<>();
//保存当前结果
List<Integer> list = new ArrayList<>();
DFS(candidates, list, 0, target, 0);
return ret;
}
}
活字印刷
你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。
注意:本题中,每个活字字模只能使用一次。
示例 1:
输入:“AAB”
输出:8
解释:可能的序列为 “A”, “B”, “AA”, “AB”, “BA”, “AAB”, “ABA”, “BAA”。
示例 2:
输入:“AAABBC”
输出:188
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-tile-possibilities
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
class Solution {
private void DFS(String tiles, Set<String> ret, List<Integer> usedIndex, String cur) {
//如果此时cur不为空就加入到set中
if (cur.length() != 0) {
ret.add(cur);
}
//遍历title是中的每一个字母
for (int i = 0; i < tiles.length(); i++) {
//每个字母不能重复使用 所以下标只要用过就不能再用
if (usedIndex.contains(i)) {
continue;
}
//使用这个下标
usedIndex.add(i);
DFS(tiles, ret, usedIndex, cur + tiles.charAt(i));
//回溯 取消这个下标的使用
usedIndex.remove(usedIndex.size() - 1);
}
}
public int numTilePossibilities(String tiles) {
//初始化一个set保存单词
Set<String> ret = new HashSet<>();
//使用一个链表保存使用过得下标
List<Integer> usedIndex = new ArrayList<>();
DFS(tiles, ret, usedIndex, "");
return ret.size();
}
}
N皇后
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 代码
/**
* Created with IntelliJ IDEA.
* Description: If you don't work hard, you will a loser.
* User: Listen-Y.
* Date: 2020-09-25
* Time: 11:02
*/
class Solution {
//保存x y节点的node
static class Node {
public int row;
public int col;
public Node(int row, int col) {
this.row = row;
this.col = col;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (!(o instanceof Node)) {
return false;
}
Node cur = (Node) o;
return cur.col == this.col && cur.row == this.row;
}
}
//用于保存所有的解法
private List<List<Node>> solutions;
private void DFS(List<Node> queen, int curRow, int n) {
//如果当前保存的queen的数量等于了n就说明是一种解决方案
if (queen.size() == n) {
//拷贝一份当前的queen
List<Node> curQueen = new ArrayList<>();
for (Node node : queen) {
curQueen.add(node);
}
solutions.add(curQueen);
return;
}
//遍历这一行的每一个位置
for (int col = 0; col < n; col++) {
//判断此时的位置是否可以放置皇后
if (can(queen, curRow, col)) {
queen.add(new Node(curRow, col));
DFS(queen, curRow + 1, n);
//回溯
queen.remove(queen.size() - 1);
}
}
}
private boolean can(List<Node> queen, int row, int col) {
//遍历此时保存的每一个皇后的node 看当前位置是否合法
for (Node node : queen) {
if (node.col == col || node.row + node.col == row + col || node.row - node.col == row - col) {
return false;
}
}
return true;
}
private List<List<String>> makeRet(int n) {
List<List<String>> ret = new ArrayList<>();
//遍历结果集
for (List<Node> list : solutions) {
//初始化一个保存结果的链表
List<String> curRet = new ArrayList<>();
for (int row = 0; row < n; row++) {
StringBuilder str = new StringBuilder();
for (int col = 0; col < n; col++) {
//如果当前位置是皇后就保存Q 不是就保存一个.
if (list.contains(new Node(row, col))) {
str.append("Q");
} else {
str.append(".");
}
}
curRet.add(str.toString());
}
ret.add(curRet);
}
return ret;
}
public List<List<String>> solveNQueens(int n) {
//初始化
solutions = new ArrayList<>();
List<Node> queen = new ArrayList<>();
DFS(queen, 0, n);
//结果导出
return makeRet(n);
}
}