- 解数独(37)
问题描述
给定一个9×9的数独棋盘,部分数字已填入,需要填充剩余的空格,使得每行、每列以及每个3×3的子网格内的数字均为1-9且不重复。
代码实现
你的代码使用了回溯法来解决数独问题。核心思路如下:
预处理冲突状态:
使用三个二维布尔数组 ca、cb 和 cc 分别记录行、列和九宫格的冲突状态。
遍历棋盘,初始化这些冲突状态。
回溯搜索:
从左上角开始,逐个填充空格。
对于每个空格,尝试填入1-9的数字,检查是否满足行、列和九宫格的约束。
如果填入的数字合法,则递归处理下一个空格;如果递归返回 true,则成功;否则回溯并尝试下一个数字。
如果所有数字都不满足,则返回 false。
关键点
冲突状态的高效存储:通过布尔数组快速判断数字是否可用。
回溯法的终止条件:当遍历到棋盘外时,表示所有空格已正确填充。
优化搜索顺序:跳过已填充的格子,直接找到下一个空格。
点击查看代码
//37. 解数独
public void solveSudoku(char[][] board) {
boolean[][] ca = new boolean[9][9];//记录行冲突状态
boolean[][] cb = new boolean[9][9];//记录列冲突状态
boolean[][] cc = new boolean[9][9];//记录九宫格冲突状态 index = (i/3) *3 +j/3
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
char ch = board[i][j];
ca[i][ch-'1']=true;
cb[j][ch-'1']=true;
cc[(i/3) *3 +j/3][ch-'1']=true;
}
}
}
solve(0,0,board,ca,cb,cc);
}
private boolean solve(int i,int j,char[][] board,boolean[][] ca,boolean[][] cb,boolean[][] cc) {
while (board[i][j] != '.') {
j++;
if (j >= 9) {
j = 0;
i++;
}
if (i>=9) return true;
}
for (int k = 1; k < 10; k++) {
if (ca[i][k-1]||cb[j][k-1]||cc[(i/3) *3 +j/3][k-1]) continue;
board[i][j]=(char)(k+'0');
ca[i][k-1]=true;
cb[j][k-1]=true;
cc[(i/3) *3 +j/3][k-1]=true;
if (solve(i,j,board,ca,cb,cc)) return true;
board[i][j]='.';
ca[i][k-1]=cb[j][k-1]=cc[(i/3) *3 +j/3][k-1]=false;
}
return false;
}
- N皇后(51)
问题描述
在n×n的棋盘上放置n个皇后,使得它们互不攻击(即任意两个皇后不在同一行、同一列或同一斜线上)。返回所有可能的放置方案。
代码实现
你的代码同样使用了回溯法来解决N皇后问题。核心思路如下:
冲突状态的存储:
使用三个布尔数组 ca、cb 和 cc 分别记录列、右斜线和左斜线的冲突状态。
列的索引为列号,右斜线的索引为 i + j,左斜线的索引为 n - 1 - (i - j)。
回溯搜索:
从第一行开始,逐行放置皇后。
对于每一行的每一列,检查是否满足列和斜线的约束。
如果合法,则递归处理下一行;如果递归返回成功,则将当前方案加入结果列表。
回溯时恢复冲突状态。
关键点
冲突状态的高效存储:通过布尔数组快速判断皇后是否可以放置。
回溯法的终止条件:当放置完所有行时,表示找到一个合法方案。
优化存储:使用一维数组存储列和斜线的冲突状态,节省空间。
点击查看代码
//51. N 皇后
public List<List<String>> solveNQueens(int n) {
List<List<String>> res = new ArrayList<>();
boolean[] ca = new boolean[n];//记录列冲突,行是层级,不用记录
boolean[] cb = new boolean[2 * n - 1];//记录右斜线冲突 index=i+j
boolean[] cc = new boolean[2 * n - 1];//记录左斜线冲突 index=(n-1)-(i-j)
char[][] table = new char[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(table[i], '.');//Arrays.fill()只能填充一维数组
}
dfsSolveNQueens(0,n,table,ca,cb,cc,res);
return res;
}
private void dfsSolveNQueens(int i,int n,char[][] table,boolean[] ca,boolean[] cb,boolean[] cc,List<List<String>> res){
if (i==n){
List<String> list = new ArrayList<>();
for (char[] chars : table) {
list.add(new String(chars));
}
res.add(list);
return;
}
for (int j = 0; j < n; j++) {
if (ca[j]||cb[i+j]||cc[n-1-(i-j)]) continue;
table[i][j]='Q';
cc[n-1-(i-j)] =cb[i+j] =ca[j] = true;
/*cb[i+j] = true;
cc[n-1-(i-j)] = true;*/
dfsSolveNQueens(i+1,n,table,ca,cb,cc,res);
table[i][j]='.';
cc[n-1-(i-j)] =cb[i+j] =ca[j] = false;
/*cb[i+j] = false;
cc[n-1-(i-j)] = false;*/
}
}
- 重新安排行程(332)
问题描述
给定一系列机票,每张机票包含起点和终点。要求重新安排行程,使得行程按字典序最小。
代码实现
你的代码使用了深度优先搜索(DFS)+ 邻接表来解决行程问题。核心思路如下:
构建图:
使用哈希表 graph 存储邻接表,键为起点,值为优先队列(存储按字典序排列的终点)。
DFS搜索:
从起点 "JFK" 开始,按字典序选择下一个目的地。
每次选择一个目的地后,递归处理下一个节点。
回溯时将当前机场加入路径(使用链表的 addFirst 方法,确保路径按逆序存储)。
优化:
使用优先队列确保每次选择的下一个目的地是字典序最小的。
使用链表存储路径,方便在回溯时插入当前节点。
关键点
图的存储:使用邻接表存储图,方便按字典序选择下一个节点。
DFS的终止条件:当所有机票都使用完时,返回路径。
回溯法的优化:通过链表的逆序存储,避免额外的路径反转操作。
点击查看代码
//332. 重新安排行程
/*List<String> res ;//效率太低
public List<String> findItinerary(List<List<String>> tickets) {
LinkedList<String> path = new LinkedList<>();
boolean[] used = new boolean[tickets.size()];
path.add("JFK");
dfsFindItinerary(tickets,used,path,"JFK");
return res;
}
private void dfsFindItinerary(List<List<String>> tickets,boolean[] used,LinkedList<String> path,String start) {
if (path.size() == tickets.size()+1) {
if (res==null) {
res = new ArrayList<>(path);
} else {
for (int i = 0; i < res.size(); i++) {
if(res.get(i).compareTo(path.get(i))>0){
res=new ArrayList<>(path);
break;
} else if (res.get(i).compareTo(path.get(i))<0) {
break;
}
}
}
return;
}
for (int i = 0; i < tickets.size(); i++) {
if (used[i]||(!tickets.get(i).get(0).equals(start))) continue;
path.add(tickets.get(i).get(1));
used[i] = true;
dfsFindItinerary(tickets,used,path,tickets.get(i).get(1));
path.removeLast();
used[i] = false;
}
}*/
// 使用邻接表存储图
private Map<String, PriorityQueue<String>> graph;
public List<String> findItinerary(List<List<String>> tickets) {
// 初始化邻接表
graph = new HashMap<>();
for (List<String> ticket : tickets) {
String from = ticket.get(0);
String to = ticket.get(1);
if(!graph.containsKey(from)) graph.put(from, new PriorityQueue<>());// 使用优先队列保持字典序
graph.get(from).offer(to);
}
LinkedList<String> path = new LinkedList<>();
dfs("JFK", path);
return path;
}
private void dfs(String airport, LinkedList<String> path) {
PriorityQueue<String> destinations = graph.get(airport);
while (destinations != null && !destinations.isEmpty()) {
String next = destinations.poll(); // 按字典序选择下一个目的地
dfs(next, path);
}
// 将当前机场加入路径(回溯时添加到路径中),终点最先添加
path.addFirst(airport);
}