[中等]全排列
class Solution {
public :
vector< vector< int >> ans;
vector< int > num;
int flag[ 10 ] ;
vector< vector< int >> permute ( vector< int > & nums) {
recall ( nums) ;
return ans;
}
void recall ( vector< int > & nums) {
if ( num. size ( ) == nums. size ( ) ) {
ans. push_back ( num) ;
}
for ( int i = 0 ; i < nums. size ( ) ; i++ ) {
if ( flag[ i] == 0 ) {
flag[ i] = 1 ;
num. push_back ( nums[ i] ) ;
recall ( nums) ;
flag[ i] = 0 ;
num. pop_back ( ) ;
}
}
}
} ;
[中等]全排列 II
原题链接 题解 在全排列问题的回溯法题解基础上,加上剪枝的思路,先对nums
中的数字进行排列,则重复的数字会靠在一起,例如2,2,2,2
,保证他们按照固定的顺序被选中,则判断思路是,如果前一个数字与当前数字相同,且前一个数字还没有被选,则排除这个分支。也就是使用continue
语句跳过目前这个分支
vector< vector< int >> ans;
vector< int > num;
int flag[ 10 ] ;
vector< vector< int >> permuteUnique ( vector< int > & nums) {
sort ( nums. begin ( ) , nums. end ( ) ) ;
recall ( nums) ;
return ans;
}
void recall ( vector< int > & nums) {
if ( num. size ( ) == nums. size ( ) ) {
ans. push_back ( num) ;
}
for ( int i = 0 ; i < nums. size ( ) ; i++ ) {
if ( flag[ i] == 0 ) {
if ( i- 1 >= 0 )
if ( flag[ i- 1 ] == 0 && nums[ i- 1 ] == nums[ i] )
continue ;
flag[ i] = 1 ;
num. push_back ( nums[ i] ) ;
recall ( nums) ;
flag[ i] = 0 ;
num. pop_back ( ) ;
}
}
}
[中等]组合
class Solution {
public :
vector< vector< int >> ans;
vector< int > num;
vector< vector< int >> combine ( int n, int k) {
recall ( n, k, 1 ) ;
return ans;
}
void recall ( int n, int k, int p) {
if ( num. size ( ) == k) {
ans. push_back ( num) ;
return ;
}
for ( int i= p; i<= n; i++ ) {
num. push_back ( i) ;
recall ( n, k, i+ 1 ) ;
num. pop_back ( ) ;
}
}
} ;
[中等]组合总和
原题链接 题解 涉及剪枝问题,其实动手画一下回溯路径就比较好理解了
class Solution {
public :
vector< vector< int >> ans;
vector< int > num;
vector< vector< int >> combinationSum ( vector< int > & candidates, int target) {
recall ( candidates, 0 , target, 0 ) ;
return ans;
}
void recall ( vector< int > & nums, int sum, int target, int k) {
if ( sum == target) {
ans. push_back ( num) ;
}
else if ( sum > target) {
return ;
}
for ( int i = k; i < nums. size ( ) ; i++ ) {
num. push_back ( nums[ i] ) ;
sum += nums[ i] ;
recall ( nums, sum, target, i) ;
sum -= nums[ i] ;
num. pop_back ( ) ;
}
}
} ;
[中等]组合总和 II
原题链接 题解 就是前面的全排列 II
和组合总和
两个问题的剪枝思路的合并,给传入的参数排序之后 1.对相同的数字,要按照原先的顺序选取 2.限制同组合的反复选取,比如[1,2,5]
在第一个分支中就取到,之后取了2之后就不在考虑取1,因为选取元素[1,2]
与选取元素[2,1]
从集合角度考虑并无不同。
class Solution {
public :
vector< vector< int >> ans;
vector< int > num;
int flag[ 110 ] ;
vector< vector< int >> combinationSum2 ( vector< int > & candidates, int target) {
sort ( candidates. begin ( ) , candidates. end ( ) ) ;
recall ( candidates, target, 0 , 0 ) ;
return ans;
}
void recall ( vector< int > & nums, int target, int sum, int k) {
if ( sum == target) {
ans. push_back ( num) ;
}
else if ( sum> target) {
return ;
}
for ( int i = k; i < nums. size ( ) ; i++ ) {
if ( flag[ i] == 0 ) {
if ( i- 1 >= 0 )
if ( flag[ i- 1 ] == 0 && nums[ i- 1 ] == nums[ i] )
continue ;
flag[ i] = 1 ;
sum += nums[ i] ;
num. push_back ( nums[ i] ) ;
recall ( nums, target, sum, i) ;
flag[ i] = 0 ;
sum -= nums[ i] ;
num. pop_back ( ) ;
}
}
}
} ;
[中等]复原 IP 地址
原题链接 题解 这题卡了很久 ①字符串的操作要熟练一点,包括substr(),earse(),insert()
的用法 ②在回溯法的循环中记得要加上判断i < s.size() && i < f + 3
:每一个子段最多3位,并且不能越界
class Solution {
public :
vector< string> ans;
vector< string> restoreIpAddresses ( string s) {
string str = "" ;
recall ( s, 0 , 0 , str) ;
return ans;
}
void recall ( string s, int f, int k, string str) {
if ( k == 4 ) {
if ( f != s. size ( ) ) return ;
str. erase ( str. size ( ) - 1 , 1 ) ;
ans. push_back ( str) ;
str. insert ( str. size ( ) , 1 , '.' ) ;
return ;
}
else if ( f == s. size ( ) ) return ;
for ( int i = f; i < s. size ( ) && i < f + 3 ; i++ ) {
if ( lawful ( s, f, i) ) {
str. insert ( str. size ( ) , s, f, i - f + 1 ) ;
str. insert ( str. size ( ) , 1 , '.' ) ;
recall ( s, i + 1 , k + 1 , str) ;
str. erase ( str. size ( ) - ( i - f + 2 ) , i - f + 2 ) ;
}
}
}
bool lawful ( string s, int begin, int end) {
string flag = s. substr ( begin, end - begin + 1 ) ;
if ( flag. size ( ) > 1 && flag[ 0 ] == '0' ) return false ;
stringstream strs;
int num;
strs << flag;
strs >> num;
if ( num > 255 ) return false ;
return true ;
}
} ;
[中等]括号生成
原题链接 题解 leftk表示(还可以添加的个数,rightk表示)还可以添加的个数,并且rightk要大于等于leftk
class Solution {
public :
vector< string> ans;
vector< string> generateParenthesis ( int n) {
string str;
recall ( str, n, n) ;
return ans;
}
void recall ( string str, int leftk , int rightk) {
if ( leftk == 0 && rightk== 0 ) {
ans. push_back ( str) ;
}
if ( leftk> 0 ) {
str += '(' ;
recall ( str, leftk- 1 , rightk) ;
str. erase ( str. size ( ) - 1 , 1 ) ;
}
if ( rightk> leftk && rightk> 0 ) {
str += ')' ;
recall ( str, leftk, rightk- 1 ) ;
str. erase ( str. size ( ) - 1 , 1 ) ;
}
}
} ;
[中等]子集
原题链接 题解 比较简单的回溯法,剪枝的主要思路就是保证递归循环之中选的数在k之后。
class Solution {
public :
vector< vector< int >> ans;
vector< int > num;
vector< vector< int >> subsets ( vector< int > & nums) {
ans. push_back ( num) ;
sort ( nums. begin ( ) , nums. end ( ) ) ;
recall ( nums, 0 ) ;
return ans;
}
void recall ( vector< int > & nums, int k) {
for ( int i= 0 ; i< nums. size ( ) ; i++ ) {
if ( i>= k) {
num. push_back ( nums[ i] ) ;
ans. push_back ( num) ;
recall ( nums, i+ 1 ) ;
num. pop_back ( ) ;
}
}
}
} ;
[中等]单词搜索
原题链接 题解 ①调用recall()
回溯函数的时候,每一步return最好是设置当前标记为0flag[i][j]=0
,恢复现场 ②如果你在return true
的时候觉得可以不返回现场,又把flag
数组设置为全局变量,可能会出现力扣和vs运行情况不同的问题,因为力扣同时会测多组数据,恢复现场没有做好,容易使全局中的flag
标记没有回到清零的状态,会使第一个样例之后的部分样例结果错误。
class Solution {
public :
int flag[ 7 ] [ 7 ] ;
bool exist ( vector< vector< char >> & board, string word) {
for ( int i = 0 ; i < board. size ( ) ; i++ )
for ( int j = 0 ; j < board[ 0 ] . size ( ) ; j++ )
if ( board[ i] [ j] == word[ 0 ] ) {
if ( recall ( board, word, 0 , i, j) ) return true ;
}
return false ;
}
bool recall ( vector< vector< char >> & board, string word, int k, int x, int y) {
flag[ x] [ y] = 1 ;
if ( board[ x] [ y] != word[ k] ) {
flag[ x] [ y] = 0 ;
return false ;
}
if ( k == word. size ( ) - 1 ) {
flag[ x] [ y] = 0 ;
return true ;
}
if ( x > 0 && ! flag[ x - 1 ] [ y] ) {
if ( recall ( board, word, k + 1 , x - 1 , y) ) {
flag[ x] [ y] = 0 ;
return true ;
}
}
if ( x < board. size ( ) - 1 && ! flag[ x + 1 ] [ y] ) {
if ( recall ( board, word, k + 1 , x + 1 , y) ) {
flag[ x] [ y] = 0 ;
return true ;
}
}
if ( y > 0 && ! flag[ x] [ y - 1 ] ) {
if ( recall ( board, word, k + 1 , x, y - 1 ) ) {
flag[ x] [ y] = 0 ;
return true ;
}
}
if ( y < board[ x] . size ( ) - 1 && ! flag[ x] [ y + 1 ] ) {
if ( recall ( board, word, k + 1 , x, y + 1 ) ) {
flag[ x] [ y] = 0 ;
return true ;
}
}
flag[ x] [ y] = 0 ;
return false ;
}
} ;
[困难]N 皇后
原题链接 题解 放置皇后主要有四种需要判断的情况 ①同一行仅一个 ②同一列仅一个 ③同一正对角线仅一个 ④同一副对角线仅一个 递归的时候按逐行操作的方式递归,避免了同一行放置皇后,同时在每行的循环中,对同一列、主对角线、副对角线的情况进行判断。
class Solution {
public :
vector< vector< string>> ans;
vector< vector< string>> solveNQueens ( int n) {
string str = "" ;
for ( int i= 0 ; i< n; i++ ) str+= "." ;
vector< string> board ( n, str) ;
dfs ( board, 0 , n) ;
return ans;
}
void dfs ( vector< string> board, int k, int n) {
if ( k== n) {
ans. push_back ( board) ;
return ;
}
for ( int j= 0 ; j< n; j++ ) {
if ( board[ k] [ j] == '.' ) {
int flag= 0 ;
int line1 = k- j;
int line2 = k+ j;
for ( int i= k; i>= 0 ; i-- ) {
if ( board[ i] [ j] == 'Q' ) { flag = 1 ; break ; }
if ( i - line1 >= 0 && board[ i] [ i - line1] == 'Q' ) { flag = 1 ; break ; }
if ( line2 - i < n && board[ i] [ line2 - i] == 'Q' ) { flag = 1 ; break ; }
}
if ( ! flag) {
board[ k] [ j] = 'Q' ;
dfs ( board, k+ 1 , n) ;
board[ k] [ j] = '.' ;
}
}
}
}
} ;