最近递归看多了,貌似也看出花样了,今天来分门别类的看看。
首页要明白,需要求得所有解的时候,不用多想,用递归!虽然大多数递归会比较耗时,但是这里用其他的思路一样耗时,谁让解空间那么大呢,呵呵。
一、求排列
无话可说,直接递归呈上,那么递归函数需要哪些参数呢?看下面。
permutation(int a[], int out, int used[], int len, int lev); 其中
out[] -- 表示结果数组
used[] -- 表示还有哪些元素没有被枚举到,这个参数可以说是排列枚举中独有的,回溯时不要忘记恢复原值!
len, lev -- 就是枚举中很常见的常数了,一个是限长,一个是当前需要枚举的位置。
下面给出求字符串所有排列的代码。
void permutate(char str[]){
int len=strlen(str);
char out[len+1];
int used[len];
for(int i=0;i<len;i++) used[i]=0;
out[len]='\0';
doPermutate(str, out, used, len, 0);
}
void doPermutate(char str[], char out[], int used[], int len, int lev){
if(lev==len){
cout<<out<<endl; //out已经被填充满了
return;
}
for(int i=0;i<len;i++){ //枚举当前level上的候选集
if(used[i]==0){
out[lev]=str[i];
used[i]=1;
doPermutate(str,out,used,len,lev+1);
used[i]=0; //用过了就恢复原状,因为要找出所有的情况
}
}
}
二、求所有子集合
较上面的排列不同的是,子集合的结果长度是变化的,即这里不能再用一个len来表示结果的长度了,那么怎么解决这个问题呢?
自然而然,我们可以想到下面的代码
class Solution {
public:
vector<vector<int> > subsets(vector<int> &S) {
vector<vector<int> > res;
vector<int> v;
doSubsets(S, res, v, 0);
return res;
}
void doSubsets(vector<int> &S, vector<vector<int> > & res, vector<int>& v, int lev){
res.push_back(v); //结果集不用等到lev==S.size()再处理
//if(lev==S.size()) return;
for(int i=lev;i<S.size();i++){ //i=lev来控制枚举方向,当前位向右
v.push_back(S[i]);
doSubsets(S, res, v, i+1); //lev=i+1表示当前位置的下一位!
v.pop_back();
}
}
};
其中,思路是逐个把每个元素开头的所有集合都枚举完,例如 {1, 2, 3}的子集{1} {1,2}, {1,2, 3}, {2}, {2,3}, {3}。这里很重要的是
1,结果集不用等到lev==S.size()再处理,和排列枚举不同
2,对于当前lev,枚举的可选值,只能为其后面的值,不能为前面的,故i=lev。
3,下一位要枚举的位置,则为当前选择的位置的下一位,lev=i+1;
如果枚举是按照位置变化的,那么需要需要添加一个新的参数start, 表示下一次枚举的位置,而lev表示当期枚举的位数,最大不超过len。
class Solution {
public:
vector<vector<int> > subsets(vector<int> &S) {
vector<vector<int> > res;
vector<int> v;
for(int i=0;i<=S.size();i++)
doSubsets(S, res, v, i, 0,0);
return res;
}
void doSubsets(vector<int> &S, vector<vector<int> > & res, vector<int>& v, int len, int lev, int start){
if(lev==len){
res.push_back(v);
return;
}
for(int i=start;i<S.size();i++){
v.push_back(S[i]);
doSubsets(S, res, v, len, lev+1, i+1); //这里lev=lev+1, 而start=i+1;
v.pop_back(); //回溯时依然需要将值还原
}
}
};
三、搜索最优解
不同于前面说的枚举所有解,这里需要找到所有枚举中“某一个”符合要求的解。例如八皇后问题,数独问题。那么应该对上面的代码如何做相应的修改呢?首先要明确,只有在错误的路径上才需要返回,否则不需要!给出数独的代码
class Solution {
public:
void solveSudoku(vector<vector<char> >& board) {
doSolveSudoku(board);
}
void getCandidates(vector<vector<char> >& board, vector<char>& v, int i, int j){
int flag[10];
for(int k=1;k<10;k++) flag[k]=k;
for(int k=0;k<board.size();k++){
if(board[i][k]!='.'){ flag[board[i][k]-'0']=0;}
if(board[k][j]!='.') flag[board[k][j]-'0']=0;
}
for(int k1=(i/3)*3;k1<(i/3)*3+3;k1++){
for(int k2=(j/3)*3;k2<(j/3)*3+3;k2++){
if(board[k1][k2]!='.')
flag[board[k1][k2]-'0']=0;
}
}
for(int k=1;k<10;k++)
if(flag[k]!=0) v.push_back(k+'0');
}
pair<int, int> findFirstEmpty(const vector< vector<char> >& board) {
for (int i=0; i<9; ++i)
for (int j=0; j<9; ++j)
if (board[i][j] == '.')
return make_pair(i, j);
return make_pair(-1, -1);
}
bool doSolveSudoku(vector<vector<char> >& board){
pair<int, int> pos=findFirstEmpty(board); //这个一定要放在最前面
if(pos.first==board.size() || pos.first==-1 && pos.second==-1)
return true;
vector<char> candidates;
candidates.clear();
getCandidates(board, candidates, pos.first, pos.second);
for(int k=0;k<candidates.size();k++){
board[pos.first][pos.second]=candidates[k];
if(doSolveSudoku(board))
return true;
board[pos.first][pos.second]='.'; //只有不正确才需要回溯!
}
return false;
}
};