做这道题最大的收获就是如何通过数据结构(Map)存储地图上的信息,如何对信息进行搜索遍历。
如果求的结果有多个,一般递归函数的返回值为void,通常要遍历整个树;如果求的结果只有一个,即仅存在一个正确解,则递归函数的返回值一般为bool类型,当满足解的条件时返回true。
以下是代码部分:
public class 重新安排行程332 {
//结果集
Deque<String> res = new LinkedList<>();
//map集合,放地图信息
Map<String, Map<String, Integer>> map;
public List<String> findItinerary(List<List<String>> tickets) {
map = new HashMap<>();
for (List<String> ticket : tickets) {
Map<String, Integer> temp;
//判断当前的起点在map中是否存在
if(map.containsKey(ticket.get(0))){
temp = map.get(ticket.get(0));
//放入的值: 终点,以及次数
temp.put( ticket.get(1), temp.getOrDefault(ticket.get(1), 0) + 1);
}else {
temp = new TreeMap<>();
temp.put( ticket.get(1), 1);
}
//将entry 放入 map
map.put(ticket.get(0), temp);
}
//踩坑,这里没将 起点 放入 res
res.add("JFK");
backtracking(tickets.size());
return new ArrayList<>(res);
}
//回溯法使用boolean类型的,一般都是只有一个解,找到那个唯一解就返回
private boolean backtracking(int n){
//边界条件
if(res.size() == n + 1)
return true;
String last = res.getLast();
if(map.containsKey(last)){
for(Map.Entry<String, Integer> entry : map.get(last).entrySet()){
int count = entry.getValue();
if(count > 0){
res.add(entry.getKey());
entry.setValue(count - 1);
if(backtracking(n))
return true;
//踩坑,这里忘记写了
res.removeLast();
entry.setValue(count);
}
}
}
return false;
}
}
思路上相较其他两题简单一些。这里我踩了两个坑,需要大家注意一下:
if(isValid(index, j, n))
flag[index][j] = true;
else
//这里踩坑,应该continue,而不是return
continue;
//判断左对角线,如果i、j相加与另一个相等就在一个左对角线: i+j == k+l
for (int k = 0; k < i; k++) {
if(i+j-k >=0 && i+j-k<n && flag[k][ i+j-k ])
return false;
}
//踩坑:这里可能会有溢出边界的情况,所以要用到 取余
//踩坑:不能取余——>既然会取到负数或者超过n,就证明它肯定是不在一条直线上的
//判断右对角线,如果 i - j == k - l
for (int k = 0; k < i; k++) {
if(k+j-i>=0 && k+j-i<n && flag[k][ k+j-i ])
return false;
}
以下是完整代码:
public class N皇后51 {
@Test
public void test(){
solveNQueens(4);
}
//定义格子是否占用
boolean[][] flag;
//定义结果集
List<List<String>> result = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
//初始化
flag = new boolean[n][n];
backtracking(n, 0);
return result;
}
private void backtracking(int n, int index){
//收集结果
if( index == n ){
//收集每个正确结果
List<String> path = new ArrayList<>();
//构造结果
for (int i = 0; i < n; i++) {
//收集每一行的字符串
StringBuilder s = new StringBuilder();
for (int j = 0; j < n; j++) {
if(flag[i][j])
s.append('Q');
else
s.append('.');
}
path.add(s.toString());
}
result.add(path);
return;
}
//在当前行中遍历每个列的情况
for (int j = 0; j < n; j++) {
if(isValid(index, j, n))
flag[index][j] = true;
else
//这里踩坑,应该continue,而不是return
continue;
//继续下一行
backtracking(n, index+1);
flag[index][j] = false;
}
}
private boolean isValid(int i, int j, int n){
//判断列,j相同就位于同一列
for (int k = 0; k < i; k++) {
if( flag[k][j] )
return false;
}
//判断左对角线,如果i、j相加与另一个相等就在一个左对角线: i+j == k+l
for (int k = 0; k < i; k++) {
if(i+j-k >=0 && i+j-k<n && flag[k][ i+j-k ])
return false;
}
//踩坑:这里可能会有溢出边界的情况,所以要用到 取余
//踩坑:不能取余——>既然会取到负数或者超过n,就证明它肯定是不在一条直线上的
//判断右对角线,如果 i - j == k - l
for (int k = 0; k < i; k++) {
if(k+j-i>=0 && k+j-i<n && flag[k][ k+j-i ])
return false;
}
return true;
}
}
这道题由于有数值的填充,所以比n皇后问题多一层循环。
这里使用二维递归的方式,两层for循环遍历位置,一层for循环遍历数值。然后进行下一个递归函数的求解。
这里注意,在递归时,i、j都是从0开始遍历的,同时每次选择一个位置后都要判断该位置是否已被填充。所以i、j不需要再参数列表中。
最后,在判断同一个九宫格时的计算方法:[i / 3 * 3, i / 3 * 3 + 3)
以下是完整代码:
public class 解数独_题解37 {
public void solveSudoku(char[][] board) {
backtracking(board);
}
//二维递归
//这里在参数表中没有涉及到 +1 ,因为移到方法体里的for循环了
private boolean backtracking(char[][] board){
//遍历位置
//这里i、j都是从0开始的,因为会有一个判断位置中是否填充的句子
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
//判断当前格子是否填充
if(board[i][j] != '.')
continue;
//遍历每个格子中的数值大小
for (int k = 1; k < 10; k++) {
board[i][j] = (char)(k+'0');
if(isValid(board, i, j)){
//递归,如果递归到最后返回true,则向上返回true
if(backtracking(board)) return true;
//回溯
board[i][j] = '.';
}else {
board[i][j] = '.';
continue;
}
}
//到了这里,说明9个数字都不行,所以return false,换其他方式
return false;
}
}
//把所有的格子都遍历完了,说明是正解,return true
return true;
}
private boolean isValid(char[][] board,int i, int j){
//判断行
for (int k = 0; k < 9; k++) {
if(k != j && board[i][k] == board[i][j])
return false;
}
//判断列
for (int k = 0; k < 9; k++) {
if(k != i && board[k][j] == board[i][j])
return false;
}
//判断九宫格 取余判断位置
//k = i%3*3 ————>踩坑,应该是 k = i/3*3
for (int k = i/3*3; k < i/3*3+3; k++) {
for (int l = j/3*3; l < j/3*3+3; l++) {
//踩坑:不能并 k != i && l != j && board[k][l] == board[i][j]
if((k != i || l != j )&& board[k][l] == board[i][j])
return false;
}
}
return true;
}
}