回溯法,采用试错的思想,分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确解答的时候,就会取消上一步或者上几步的运算,再通过其他的可能分步解答再次尝试寻找问题的答案。最经典的问题,就是八皇后问题。
1 n皇后问题
n-皇后 问题就是正确的在棋盘上面放置皇后的位置,从而使得任意两个皇后之间都无法攻击对方,攻击的方式是同行、同列或对角线。
给定 n, 要求返回n-皇后问题的所有解。
Each solution contains a distinct board configuration of the n-queens' placement, where 'Q'
and '.'
both indicate a queen and an empty space respectively.
举例,对于n为4的情况,存在如下这些解
[ [".Q..", // Solution 1 "...Q", "Q...", "..Q."], ["..Q.", // Solution 2 "Q...", "...Q", ".Q.."] ]首先我们需要一个棋盘用来保存当前的状态,也就是那里已经放置了皇后,从而在进入新的一行的时候进行判断。棋盘状态可以用n*n的矩阵保存,但是也可以使用1*n的数组,数组的下标表示棋盘的行数,数组的值便是棋盘的列数,这样就充分的表示了哪行哪列可以放置皇后。算法的结构如下:
1 初始化棋盘数组
2 行数i: 1->n
列j: 1->n
对于每一个位置(i,j),检测是否可以放皇后,检测的原则是当前的列数不与之前放置的皇后的列数相等,同时也不能处于其对角线上,这里要注意的是,
如果在对角线上,就要满足如下性质:|i - row| == |j - col|
如果不能放皇后,就列数后移一位。
如果可以放皇后,就更新棋盘数组,同时行数下移一位。
如果第i行,没有找到放置皇后的位置。
如果i ==0, 说明已经回溯到第一行了,找到了所有的可能的解,退出。
如果i != 0, 那就把上一行的皇后位置向后移动一列,然后继续在这一行寻找合法的皇后位置。
如果行数到达了最后一行,说明一个解已经找到,回溯,寻找另外一个解。
这里首先给出一个非递归的解法:
vector > solveNQueens(int n) {
int* a = new int[n];
//初始化
for(int k = 0 ; k < n ; k++)
a[k] = -100;
int j = 0,i = 0;
vector> result;
while(i < n)
{
while(j < n)
{
if(valid(i,j,a))
{
a[i] = j;
j = 0;//mark
break;
}
else
{
j++;
}
}
//如果第i行没有找到可以放置皇后的位置
if(a[i] == -100)
{
// 回溯到第一行,还是没找到可行的解,那就说明都遍历了
if(i == 0)
break;
// 否则的话回溯,并把上一行的皇后清空,再往后移动一位
else
{
--i;
j = a[i] + 1;
a[i] = -100;
continue;
}
}
//这个时候已经找到了全部需要的皇后
if(i == n -1)
{
pushIn(result,a,n);
//但是由于还没完全返回全部的组合,所以还要继续找
j = a[i] + 1;
a[i] = -100;
continue;
}
i++;
}
return result;
}
判断是否合法的函数:
bool valid(int row, int col, int *a)
{
for(int i = 0; i < row; i++)//a中之前的行
{
if(a[i] == col || abs(row - i) == abs(col - a[i]))
{
return false;
}
}
return true;
}
void pushIn(vector> &result, int *a,int n)
{
vector tmp1;
for(int i = 0; i < n; i++)
{
string tmp2;
for(int j = 0; j < n ; j++)
{
if(a[i] == j)
{
tmp2+="Q";
}
else tmp2+=".";
}
tmp1.push_back(tmp2);
}
result.push_back(tmp1);
}
再给出一个递归的解法:
void queen(int row, int *a, int n,vector> &result)
{
if (n == row) //如果已经找到结果,则打印结果
pushIn(result,a,n);
else
{
for (int k=0; k < n; k++)
{ //试探第row行每一个列
if (valid(row, k,a))
{
a[row] = k; //放置皇后
queen(row + 1,a,n,result); //继续探测下一行
//返回上一级的时候要重新初始化,
a[row] = -100;
}
}
}
}
vector > solveNQueens(int n) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
int* a = new int[n];
//初始化
for(int k = 0 ; k < n ; k++)
a[k] = -100;
vector> result;
queen(0,a,n,result);
return result;
}
2 Unique Path独一无二的路
一个机器人处在一个m*n的方格区的左上角。机器人只能向下或者向右走。机器人的最终目的是右下角。求出一共有多少可能的独一无二的路径?
Note: m and n will be at most 100.
由于每次都只能向下或向右走,所以当前节点i,j 处的路径数应该等于其上部的节点i-1,j 以及其左部的节点i,j-1 的路径数之和。
path(i,j) = path(i-1,j) + path(i,j-1);
先给出一个比较直观的递归的解法:
int backtrack(int r, int c, int m, int n) {
if (r == m && c == n)
return 1;
if (r > m || c > n)
return 0;
return backtrack(r+1, c, m, n) + backtrack(r, c+1, m, n);}
const int M_MAX = 100;const int N_MAX = 100;
int backtrack(int r, int c, int m, int n, int mat[][N_MAX+2]) {
if (r == m && c == n)
return 1;
if (r > m || c > n)
return 0;
//只要当没有计算的时候,才进行运算,否则的话就直接跳过。两个方向都计算了之后,再把结果给加起来。
if (mat[r+1][c] == -1)
mat[r+1][c] = backtrack(r+1, c, m, n, mat);
if (mat[r][c+1] == -1)
mat[r][c+1] = backtrack(r, c+1, m, n, mat);
return mat[r+1][c] + mat[r][c+1];}
int bt(int m, int n) {
int mat[M_MAX+2][N_MAX+2];
for (int i = 0; i < M_MAX+2; i++) {
for (int j = 0; j < N_MAX+2; j++) {
mat[i][j] = -1;
}
}
return backtrack(1, 1, m, n, mat);}
给定一系列的数的集合S, 返回所有可能的子集:
注意:
举例,
If S = [1,2,3]
, a solution is:
[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
void subsetsAll(vector> & ans, vector tmp,vector &S, int n,int k,int index)
{
if(k == 0)
{
if(find(ans.begin(),ans.end(),tmp) == ans.end())
ans.push_back(tmp);
}
else if(k > 0 && index < n)
{
tmp.push_back(S.at(index));
subsetsAll(ans,tmp,S,n,k-1,index+1);//已经找到了第一个,在之后里面寻找k-1个。
tmp.pop_back();
subsetsAll(ans,tmp,S,n,k,index+1); //跳过第一个,在之后的里面寻找k个。
}
}
vector > subsets(vector &S) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
sort(S.begin(),S.end());
int n = S.size();
vector tmp;
vector> ans;
if(n==0) {ans.push_back(tmp); return ans;}
for(int i=0;i<=n;i++)
{
subsetsAll(ans,tmp,S,n,i,0);
}
}