回溯法(backtracking)是暴力搜索法中的一種。對於某些計算問題而言,回溯法是一種可以找出所有(一部分)解的一般性算法,尤其適用於約束滿足問題(在解決約束滿足問題時,我們逐步構造更多的候選解,並且在確定某一部分候選解不可能補全成正確解之後放棄繼續搜索這個部分候選解本身及其可以拓展出的子候選解,轉而測試其他的部分候選解)–維基百科
通俗點理解就是回溯法其實是一個搜索過程,按照條件搜索,當搜索到某一步時,發現並不能達到目標時,就退回來一步重新選擇。此路不同我就退回來再重新找路。回溯法容易忽略的一點是回溯時要恢復成原先的狀態。
回溯法是算法思想,通常使用遞歸方式實現,有時候適當的剪枝能夠優化複雜度。僞代碼如下:
result = []
void backtrack(路徑, 選擇列表):
if 滿足結束條件:
result.add(路徑)
return
for 選擇 in 選擇列表:
做選擇
backtrack(路徑, 選擇列表)
撤銷選擇
46. 全排列
題目描述:
給定一個沒有重複數字的序列,返回其所有可能的全排列。
示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
思路:
從第一個數字起每個數分別與它後面的數字交換。
代碼:
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>>res;
backtrack(nums, res, 0);
return res;
}
void backtrack(vector<int>& nums, vector<vector<int>>& res, int n){
if(n==nums.size())
{
res.push_back(nums);
}
for(int i=n;i<nums.size();i++)
{
swap(nums[n], nums[i]);
backtrack(nums, res, n+1);
swap(nums[n], nums[i]); //回溯
}
}
void swap(int& a, int& b)
{
int tmp=a;
a=b;
b=tmp;
}
};
47. 全排列II
題目描述:
給定一個可包含重複數字的序列,返回所有不重複的全排列。
示例:
輸入: [1,1,2]
輸出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
思路:
去重的全排列就是從第一個數字起每個數分別與它後面非重複出現的數字交換。
代碼:
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
backtrack(nums, res, 0);
return res;
}
void backtrack(vector<int>& nums, vector<vector<int>>& res, int n){
if(n==nums.size())
{
res.push_back(nums);
}
for(int i=n;i<nums.size();i++)
{
if(isValide(nums, n, i))
{
swap(nums[n], nums[i]);
backtrack(nums, res, n+1);
swap(nums[n], nums[i]);
}
}
}
void swap(int& a, int& b)
{
int tmp=a;
a=b;
b=tmp;
}
bool isValide(vector<int>& nums, int begin, int end){
for(int i=begin;i<end;i++){
if(nums[end]==nums[i])
{
return false;
}
}
return true;
}
};
22. 括號生成
題目描述
給出 n 代表生成括號的對數,請你寫出一個函數,使其能夠生成所有可能的並且有效的括號組合。
思路:
當有一個位置時,我們放置‘(’,爲了使得‘(’和‘)’配對出現,在‘)’不超過‘(’的數量時,放置一個‘)’。
代碼:
class Solution {
public:
void backtrack(vector<string>&res, int n, string s, int start, int end){
if(s.length() == 2 * n){
res.push_back(s);
}
if(start<n){
backtrack(res, n, s+'(',start+1, end);
}
if(end<start){
backtrack(res, n, s+")",start, end+1);
}
}
vector<string> generateParenthesis(int n) {
vector<string> res;
backtrack(res, n, "", 0, 0);
return res;
}
};
37.解數獨
題目描述:
編寫一個程序,通過已填充的空格來解決數獨問題。
一個數獨的解法需遵循如下準則:
- 數字1-9在每一行只能出現一次
- 數字1-9在每一列只能出現一次
- 數字1-9在每一個粗實線分割的3x3宮內只能出現一次。
空白格用‘.’表示。
思路:
定義三個數組row,col和box分別記錄每一行中已有的數字,每一類中已有的數字和3*3宮內的已有的數字。遍歷到第r行和第c列元素時,如果不爲空,則向一下元素遍歷,如果爲空,遍歷數字1-9,數字同時不在以上定義的三個數組時,纔可向下一步進行探索。
代碼:
class Solution {
public:
int row[10][10], col[10][10], box[10][10];
bool isSovled=false;
void solveSudoku(vector<vector<char>>& board) {
memset(row, 0, 10*10);
memset(col, 0, 10*10);
memset(box, 0, 10*10);
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]!='.'){
int index = 3*(i/3) + j/3;
int num = board[i][j] - '0';
row[i][num] = 1;
col[j][num] = 1;
box[index][num] = 1;
}
}
}
backtrack(0,0,board);
}
void backtrack(int r, int c, vector<vector<char>>& board){
if(isSovled){
return;
}
if(r>=9){
isSovled=true;
return;
}
if(board[r][c]!='.'){
if(c<8){
backtrack(r, c+1, board);
}else if(c == 8){
backtrack(r+1, 0, board);
}
if(isSovled){
return;
}
}else{
int index = 3*(r/3) + c/3;
for(int num=1;num<=9;num++){
if(!row[r][num]&&!col[c][num]&&!box[index][num]){
board[r][c] = num+'0';
row[r][num] = 1;
col[c][num] = 1;
box[index][num] = 1;
if(c<8){
backtrack(r, c+1, board);
}else if(c==8){
backtrack(r+1, 0, board);
}
// 回溯
if(!isSovled){
row[r][num] = 0;
col[c][num] = 0;
box[index][num] = 0;
board[r][c] = '.';
}
}
}
}
}
};
51. N皇后
題目描述:
給定一個整數 n,返回所有不同的 n 皇后問題的解決方案。
每一種解法包含一個明確的 n 皇后問題的棋子放置方案,該方案中 ‘Q’ 和 ‘.’ 分別代表了皇后和空位。
思路:
經典的N皇后問題,我們採用回溯法進行解題。
代碼:
class Solution {
public:
vector<vector<string>> res;
vector<vector<string>> solveNQueens(int n) {
vector<string> Queen(n, string(n, '.'));
backtrack(0, n, Queen);
return res;
}
void backtrack(int r, int n, vector<string>& Queen){
if(r >= n){
res.push_back(Queen);
return;
}
for(int i=0;i<n;i++){
if(!isJudge(r, i, n, Queen)){
continue;
}
Queen[r][i] = 'Q';
backtrack(r+1, n, Queen);
Queen[r][i] = '.';
}
}
bool isJudge(int row, int col, int n, vector<string>& Queen){
for(int i=0;i<col;i++){
if(Queen[row][i]=='Q'){
return false;
}
}
for(int i=0;i<row;i++){
if(Queen[i][col]=='Q'){
return false;
}
}
for(int i = row-1, j = col-1;i>=0&&j>=0;i--,j--){
if(Queen[i][j]=='Q'){
return false;
}
}
for(int i=row-1, j=col+1;i>=0&&j<n;i--,j++){
if(Queen[i][j]=='Q'){
return false;
}
}
return true;
}
};
52. N皇后II
題目描述:
給定一個整數 n,返回 n 皇后不同的解決方案的數量。
思路:
這個N皇后變體是要求數量,上一題已經求出n皇后的所有的排列,那麼這一題在上一題的基礎上稍微改動時即可。就是在滿足條件時,由上一題將排列加入數組變成計數。
代碼:
class Solution {
public:
int count = 0;
int totalNQueens(int n) {
vector<string> Queen(n, string(n, '.'));
backtrack(0, n, Queen);
return count;
}
void backtrack(int r, int n, vector<string>& Queen){
if(r >= n){
count++;
return;
}
for(int i=0;i<n;i++){
if(!isJudge(r, i, n, Queen)){
continue;
}
Queen[r][i] = 'Q';
backtrack(r+1, n, Queen);
Queen[r][i] = '.';
}
}
bool isJudge(int row, int col, int n, vector<string>& Queen){
for(int i=0;i<col;i++){
if(Queen[row][i]=='Q'){
return false;
}
}
for(int i=0;i<row;i++){
if(Queen[i][col]=='Q'){
return false;
}
}
for(int i = row-1, j = col-1;i>=0&&j>=0;i--,j--){
if(Queen[i][j]=='Q'){
return false;
}
}
for(int i=row-1, j=col+1;i>=0&&j<n;i--,j++){
if(Queen[i][j]=='Q'){
return false;
}
}
return true;
}
};