回溯法(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;
}
};