目录
1.解数独
题解
2.单词搜索
题解
1.解数独
链接:37. 解数独
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
题解
- 上面的是判断是否是有效数独,这里是填数独。
- 这里还是用上面的三个bool类型数组,来判断这个数能不能放。
- 这里我们一格一格的放,每个格子可以放1-9中其中一个数,但是肯定会是存在 剪枝 情况的。
具体能不能放还是借助那三个bool类型数组来判断。
- 我们递归就是拿着这个棋盘,开始依次遍历,看那个是空的就开始填,填的时候判断一下能填在填不能填就不填。
- 然后能填递归到下一层,但是有可能这个能填的分支下面递归有的位置1 ~ 9都不能填的情况。
- 因此这个分支可能是得不到正确结果的,那上面怎么知道你这种情况不行的呢?
- 因此这个 递归函数 要有一个bool类型的返回值,当遇到某个格子1 ~ 9都不能填直接向上返回一个false,告诉它这个位置你填1不行,你要把这个位置填上2然后在往下试。
递归函数 参数只要把这个board给我就行了。bool dfs(board)。
class Solution {
public:
bool checkrow[9][10]={}, checkcol[9][10]={}, grip[3][3][10]={};
void solveSudoku(vector<vector<char>>& board) {
//预处理
for(int i=0; i<9; i++){
for(int j=0; j<9; j++){
if(board[i][j] != '.'){
int num = board[i][j]-'0';
checkrow[i][num] = checkcol[j][num] = grip[i/3][j/3][num] = true;
}
}
}
dfs(board,0,0);
}
bool dfs(vector<vector<char>>& board, int row, int col) {
if(row == 9) return true; // 修正终止条件
if(col == 9) return dfs(board, row+1, 0);
if(board[row][col] != '.') { // 修正非空推进
return dfs(board, row, col+1);
}
for(int i=1; i<=9; i++) { // 修正数字范围
if(!checkrow[row][i] && !checkcol[col][i] && !grip[row/3][col/3][i]) {
// 标记数字
board[row][col] = i+'0';
checkrow[row][i] = checkcol[col][i] = grip[row/3][col/3][i] = true;
if(dfs(board, row, col+1)) return true;
//!!!!!成功路径要正确终止,来满足递归的返回
// 回溯恢复
board[row][col] = '.';
checkrow[row][i] = checkcol[col][i] = grip[row/3][col/3][i] = false;
}
}
return false;
}
};
//!!!!!成功路径要正确终止,来满足递归的返回
if(dfs(board, row, col+1)) return true;
优化版本:
class Solution {
public:
bool checkrow[9][10] = {false}; // 记录行数字存在情况
bool checkcol[9][10] = {false}; // 记录列数字存在情况
bool grip[3][3][10] = {false}; // 记录九宫格数字存在情况
void solveSudoku(vector<vector<char>>& board) {
// 预处理已有数字
for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) {
if(board[i][j] != '.') {
int num = board[i][j] - '0';
mark(i, j, num, true);
}
}
}
dfs(board, 0, 0);
}
bool dfs(vector<vector<char>>& board, int row, int col) {
if(row == 9) return true; // 正确终止条件
if(col == 9) return dfs(board, row+1, 0);
// 跳过已填数字
if(board[row][col] != '.') {
return dfs(board, row, col+1);
}
for(int num = 1; num <= 9; num++) { // 修正数字范围
if(isValid(row, col, num)) {
board[row][col] = num + '0';
mark(row, col, num, true);
if(dfs(board, row, col+1)) return true;
// 回溯恢复
mark(row, col, num, false);
board[row][col] = '.';
}
}
return false;
}
private:
bool isValid(int row, int col, int num) {
return !checkrow[row][num] &&
!checkcol[col][num] &&
!grip[row/3][col/3][num];
}
void mark(int row, int col, int num, bool flag) {
checkrow[row][num] = flag;
checkcol[col][num] = flag;
grip[row/3][col/3][num] = flag;
}
};
2.单词搜索
链接:79. 单词搜索
给定一个 m x n
二维字符网格 board
和一个字符串单词 word
。如果 word
存在于网格中,返回 true
;否则,返回 false
。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
题解
给一个mxn board二维数组,让在数组中找是否存在字符串单词 word
注意只能上下左右去找,不能斜着找。
我们在这个矩阵中依次找到word里面的所有字符。
- 首先在这个矩阵里面 先一行一行的扫描 找到word里面第一个字符
- 当找到第一个字符时,就从这个字符开始去匹配其他剩余的字符,注意只能上下左右去匹配
- 如果上下左右都不匹配说明这个位置是一次失败的匹配。
- 那就在去找其他位置能匹配的第一个字符。
- 如果找到还是 上下左右去匹配,如果能匹配成功说明这个矩阵有这个单词。
如果把这道题抽象一下,你会发现刚刚的过程就是 解决迷宫常用的方式,深度优先遍历。
如果走不通就回溯一下,再往下走,直到找到一个正确结果为止。
接下来我们考虑 递归函数 如何设计
- 从某个节点开始上下左右匹配下一个字符,所以则函数体干的就是给一个位置然后 上下左右去匹配下一个字符。
- 这个参数首先把这个board给我,然后第一个字符的位置,然后给我要匹配字符串中下一个字符的位置。
- dfs(board,i,j,s,pos),注意看这个决策树我们从一个位置走可能走失败,上面调用dfs的得知道是找成功还是失败,所以dfs有一个bool类型的返回值
- bool dfs(board,i,j,s,pos) 失败就去另一条路径。
剪枝就是那一个位置能匹配就去走那一个位置。
回溯 一条路径找失败往上走就是回溯,往下走之前你弄了什么,反着来就可以了。
下面是细节问题:二维矩阵搜索中,经常要注意的细节。
不能走重复的路
就比如下面的这个位置,一直会是重复的。之前用过的下一次不能在找了。
这里我们有两种方法规避。
1. 用一个bool类型的跟原始数组大小一样的二维数组 bool visit[][]。
- 用这个数组来标记当前这个位置是否被使用过。
- 使用过就把这个位置标记成true。
- 然后到下一层的时候就考虑一下上一个位置是否是ture,是就不走,不是就可以走。
2. 修改原始矩阵的值。
- 比如说把被使用的位置的值修改成*,面试时还是要问一下能否修改,能修改再用这种方法。
- 但是还有一个问题你修改完去往下一层,然后再回来的时候你要把被修改的值在还原回来。
这里在写代码去上下左右匹配的时候有两种写法:
- 可以分别写上下左右四个函数去匹配。
- 我们可以用向量的方式,定义上下左右四个位置。
- 然后仅需一次for循环就可以把上下左右都考虑进去了。
单独看这个i,上下左右就是在原始的i基础上要么不变,要么+1,要么-1
所以可以搞一个数组 int dx[4]={0,0,1,-1}; 这个表示i接下来可能的偏移量。
同理j也有四个偏移量 int dy[4]={-1,1,0,0}; 然后这两个就可以上下凑一起使用。
class Solution {
public:
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
int m,n;
vector<vector<bool>> check;
bool exist(vector<vector<char>>& board, string word)
{
m=board.size();
n=board[0].size();
check.resize(m,vector<bool>(n,false));
// 遍历所有起点
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(board[i][j] == word[0])
{
check[i][j]=true;
if(dfs(board,i,j,word,1)) return true;
check[i][j]=false;//以这个点开始搜索,搜索完之后发现不行 //回溯
}
}
}
return false;
}
bool dfs(vector<vector<char>>& board,int i,int j, string s,int pos)
{
if(pos == s.size()) return true;
for(int k=0;k<4;k++) //来实现上下左右的移动
{
int x=i+dx[k],y=j+dy[k];
//这里的检查和bfs 是一样的
if(x >= 0 && x < m && y >= 0 && y < n && check[x][y] == false && board[x][y] == s[pos])
{
if(board[x][y]==s[pos])
{
check[x][y]=true;
if(dfs(board,x,y,s,pos+1)) return true;
check[x][y]=false;
}
}
}
return false;
}
};