剪枝&回溯
劍12 矩陣中的路徑@@
請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一格開始,每一步可以在矩陣中向左、右、上、下移動一格。如果一條路徑經過了矩陣的某一格,那麼該路徑不能再次進入該格子。例如,在下面的3×4的矩陣中包含一條字符串“bfce”的路徑(路徑中的字母用加粗標出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩陣中不包含字符串“abfb”的路徑,因爲字符串的第一個字符b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入這個格子。
示例 1:
輸入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
輸出:true
示例 2:
輸入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
輸出:false
分析:經典回溯,但此題只要求返回true or false,能返回所有路徑嗎?
friend ostream &operator<<(ostream &out, const vector<vector<char>> &board)
{
//重載 << ,用於調試輸出二維數組
for (auto i : board)
{
for (auto j : i)
{
out << j << " ";
}
out << endl;
}
out << endl;
return out;
}
bool backtrack(vector<vector<char>> &board, int row, int col,
const string &word, int idx)
{
//正確返回終止條件,找到一個正確分支即終止
if (idx == word.size())
return true;
//數組越界終止條件
if (row < 0 || row >= board.size() ||
col < 0 || col >= board[0].size())
return false;
//如果表格當前字符不等於欲匹配字符,剪掉此分支
//這樣還在增長的分支就是可能正確的分支
if (word[idx] != board[row][col])
return false;
//設置標記位,如果後續回到此位置,比對字符即知
board[row][col] = '*';
//往四個方向走,有一個方向返回true,程序返回true
if (backtrack(board, row - 1, col, word, idx + 1) ||
backtrack(board, row + 1, col, word, idx + 1) ||
backtrack(board, row, col - 1, word, idx + 1) ||
backtrack(board, row, col + 1, word, idx + 1))
return true;
//回溯。走不通時該位置還原爲本來的字母
board[row][col] = word[idx];
//cout<<board;
//沒有分支返回true。程序返回false
return false;
}
bool exist(vector<vector<char>> &board, string word)
{
if (board.empty() || board[0].empty())
return word.empty();
for (int row = 0; row < board.size(); ++row)
{
for (int col = 0; col < board[0].size(); ++col)
{
//暴力搜索每一點作爲起點
if (backtrack(board, row, col, word, 0))
return true;
}
}
return false;
};
劍13.機器人的運動範圍
地上有一個m行n列的方格,從座標 [0,0] 到座標 [m-1,n-1] 。一個機器人從座標 [0, 0] 的格子開始移動,它每次可以向左、右、上、下移動一格(不能移動到方格外),也不能進入行座標和列座標的數位之和大於k的格子。例如,當k爲18時,機器人能夠進入方格 [35, 37] ,因爲3+5+3+7=18。但它不能進入方格 [35, 38],因爲3+5+3+8=19。請問該機器人能夠到達多少個格子?
示例 1:
輸入:m = 2, n = 3, k = 1
輸出:3
示例 1:
輸入:m = 3, n = 1, k = 0
輸出:1
第一版代碼
根據上面一題直接寫出此題的代碼,提交結果爲超時,本地運行超時用例時等待很久,說明有死循環。本着“不放棄每一段代碼”的精神,我決定debug後再看書上的答案
class Solution {
public:
int K;
friend ostream &operator<<(ostream &out, const vector<vector<char>> &board)
{
for (auto i : board)
{
for (auto j : i)
{
out << j << " ";
}
out << endl;
}
out << endl;
return out;
}
bool cant_move(int row, int col, int k)
{
int cnt = 0;
while (row or col)
{
cnt += row % 10 + col % 10;
row /= 10;
col /= 10;
}
return cnt > k;
}
struct arrayHash{
//避免哈希衝突
int operator()(const array<int, 2> &p) const { return p[0] * 1e2 + p[1]; }
};
unordered_set<array<int, 2>,arrayHash> um;
void backtrack(vector<vector<char>> &board, int row, int col)
{
//以下3個條件進行剪枝
//1、數組越界終止條件
if (row < 0 || row >= board.size() ||
col < 0 || col >= board[0].size())
return;
//2、標誌位,防止重複遍歷
if ('o' != board[row][col])
return;
//3、檢測該點是否符合題目條件
if (cant_move(row, col, K))
return;
//設置標記位,如果後續回到此位置,比對字符即知
board[row][col] = '*';
//cout << board;
array<int, 2> p;
p[0] = row, p[1] = col;
if(um.find(p)==um.end()){
um.insert(p); //放入哈希表
}
//往四個方向走,
backtrack(board, row - 1, col);
backtrack(board, row + 1, col);
backtrack(board, row, col - 1);
backtrack(board, row, col + 1);
//回溯。走不通時該位置還原爲本來的字母
board[row][col] = 'o';
//cout << board;
//沒有分支返回true。程序返回false
return;
}
int movingCount(int m, int n, int k) {
vector<vector<char>> b(m,vector<char>(n,'o'));
K=k;
backtrack(b, 0, 0);
return um.size();
}
};
22. 括號生成
給出 n 代表生成括號的對數,請你寫出一個函數,使其能夠生成所有可能的並且有效的括號組合。
例如,給出 n = 3,生成結果爲:
[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
分析:純剪枝,無回溯
class Solution {
vector<string> ans;
int N;
public:
vector<string> generateParenthesis(int n) {
N = n;
DFS("",0,0);
return ans;
}
void DFS(string s,int left,int right){
//兩個剪枝條件
if(left < right) return;
if(left > N) return;
//終止遞歸條件
if(s.length() == 2*N){
ans.push_back(s);
return;
}
//分支
DFS(s+"(",left+1,right);
DFS(s+")",left,right+1);
}
};
51.N皇后
copy了份代碼,加點註釋分析下
class Solution {
public:
vector<vector<string>> ans;
vector<vector<string>> solveNQueens(int n) {
//這裏一定要寫n,給record初始化
vector<int> record(n);
//字符串s初始化爲n個點
string s="";
for(int i=0;i<n;i++){
s+='.';
}
//棋盤初始化爲n個向量,每個都是n個點,即n*n棋盤
vector<string> temp(n, s);
helper(0, n, temp, record);
return ans;
}
/*
l表示行數,n爲題目中的n,temp爲棋盤,recode記錄?
*/
void helper(int l, int n, vector<string>&temp, vector<int>& record){
//正常終止遞歸條件,即棋盤填滿了
if(l==n){
ans.push_back(temp);
return;
}
//從左到右依次填入棋子
for(int i=0;i<n;i++){
//從左到右依次填入皇后
record[l]=i;
//如果不能填入,什麼也不做,相當於剪枝
//如果能填入皇后,則填入
if(isok(record, l)){
temp[l][i]='Q';
//填入本行的皇后後,遞歸尋找下一行皇后的位置
helper(l+1, n, temp, record);
//回溯,即取消在該點填入皇后
temp[l][i]='.';
}
}
}
//檢查皇后能否填入的算法,row是遞歸程序當前的行
bool isok(vector<int>& record, int row){
for(int i=0;i<row;i++){
if(record[i]==record[row]||row-record[row]==i-record[i]||row+record[row]==i+record[i])return false;
}
return true;
}
};
以n = 4不剪枝爲例,每次遞歸程序處理一行。例如(0,0)填入後,又有(1,0)、(1,1)、(1,2)、(1,3)四種填法,第三行又有4種填法。關鍵是填入皇后(1,0)後,遞歸到下一層,遞歸返回後又重新填入(1,1),實現了每一層的4個分支
以下是我的解法:
#include <bits/stdc++.h>
using namespace std;
int cnt = 0;
void outPut(const vector<vector<char>> &b)
{
for (int i = 0; i < b.size(); i++)
{
for (int j = 0; j < b.size(); j++)
{
cout << b[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
void mark(int x, int y, vector<vector<char>> &b)
{
int n = b.size();
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
b[x][j] = '+';
b[i][y] = '+';
if (i + j == x + y or i - j == x - y)
{
b[i][j] = '+';
}
}
}
b[x][y] = 'x';
//outPut(b);
}
void dfs(int level, vector<vector<char>> &b)
{
if (level == b.size())
{
outPut(b);
cnt++;
return;
}
for (int i = 0; i < b.size(); i++)
{
if (b[level][i] == '.')
{
vector<vector<char>> tmp = b;
mark(level, i, b);
dfs(level + 1, b);
b = tmp;
//outPut(b);
}
}
}
int main()
{
int N;
cin >> N;
vector<vector<char>> b(N, vector<char>(N, '.')); //棋盤
dfs(0, b);
//cout << cnt << endl;
system("pause");
}
36.有效數獨
class Solution
{
set<char> st1;
set<char> st2;
public:
bool cheak(char c, set<char> &st)
{
if (c <= 57 && c >= 49)
{
if (st.find(c) != st.end())
return 0;
else
st.insert(c);
}
return 1;
}
bool isValidSudoku(vector<vector<char>> &board)
{
for (int i = 0; i < 9; i++)
{
st1.clear();
st2.clear();
for (int j = 0; j < 9; j++)
{
if (!cheak(board[i][j], st1))
return 0;
if (!cheak(board[j][i], st2))
return 0;
}
}
for (int u = 0; u < 3; u++)
{
for (int v = 0; v < 3; v++)
{
st1.clear();
for (int i = 3 * u; i < 3 * u + 3; i++)
{
for (int j = 3 * v; j < 3 * v + 3; j++)
{
if (!cheak(board[i][j], st1))
return 0;
}
}
}
}
return 1;
}
};
37.解數獨
class Solution {
public:
bool solveSudoku(vector<vector<char>>& board) {
for ( int i = 0 ;i < board.size();i++){
for(int j=0;j<board[i].size();j++){
if(board[i][j]=='.'){//開始填入
for(char c='1';c<='9';c++){
if(isValid(board,i,j,c)){
board[i][j]=c;//填入
if(solveSudoku(board)) return true;//解完
else board[i][j]='.';//下層返回錯,回溯
}
}
return false;//填完了1-9 都不行 就返回錯
}
}
}
return true;
}
private:
bool isValid(vector<vector<char>>& board,int row,int col,char c){
for(int i=0;i<9;i++){
if(board[i][col]==c) return false;
if(board[row][i]==c) return false;
if(board[3*(row/3)+i/3][3*(col/3)+i%3]==c) return false;
}
return true;
}
};