题目一:全排列
问题描述
46. 全排列 - 力扣(LeetCode)
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
复制代码
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
复制代码
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
复制代码
输入:nums = [1]
输出:[[1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums
中的所有整数 互不相同
解题步骤
这个题目属于排列问题,那么和我们之前做过的组合问题的区别就在于次序有别
1,2,3\],\[1,3,2\],\[2,1,3\],\[2,3,1\],\[3,1,2\],\[3,2,1\]在组合问题中是要被去重的
而在排列问题中则正是我们所要的结果
所以这一题的关键在于抛弃之前的组合去重逻辑,使用startindex缩减范围
改为只要求使用过的数不重复即可
只去重纵向的重复我们可以用used数组来实现
只要给每一个数字打上用过or没用过的标记
我们在循环中就能避免\[1,1,1\],\[1,1,2\]这样的答案
回溯三部曲
#### **1.确定参数及返回值**
依旧利用两个全局变量path,result分别存放过程结果和最终结果集
所以该函数无需返回值
根据上面的分析参数就应该为nums数组及used数组
> void backtracking(vector\\& nums,vector\\& used)
#### **2.确定终止条件**
排列问题的结果还是在叶子结点取到,并且这个答案的长度和原数组长度一致
总不能你让小朋友去排队结果弄丢一个吧
所以当path与nums数组长度一致时,把path加入result
> if(path.size()==nums.size()){
>
> result.push_back(path);
>
> }
#### **3.单层遍历逻辑**
由于我们这里不再使用startindex,每次遍历就应该从0开始,
然后借助used数组排除用过的元素
再把合适的元素加入path,并修改它的使用标志即used数组对应值
递归调用backtracking函数,再对path,used做回溯
> for(int i=0;i\
> if(used\[i\]==true){
>
> continue;
>
> }
>
> path.push_back(nums\[i\]);
>
> used\[i\]==true;
>
> backtracking(nums,used);
>
> used\[i\]=false;
>
> path.pop_back();
>
> }
最后别忘记在主函数中定义used数组并全部初始化为false
> vector\ used(nums.size(),false);
整合后的完整代码如下!
### code
```cpp
class Solution {
public:
vector path;
vector> result;
void backtracking(vector& nums,vector& used){
if(path.size()==nums.size()){
result.push_back(path);
return;
}
for(int i=0;i> permute(vector& nums) {
vector used(nums.size(),false);
backtracking(nums,used);
return result;
}
};
```
*** ** * ** ***
## 题目二:全排列②
### 问题描述
[47. 全排列 II - 力扣(LeetCode)](https://leetcode.cn/problems/permutations-ii/ "47. 全排列 II - 力扣(LeetCode)")
给定一个可包含重复数字的序列 `nums` ,***按任意顺序*** 返回所有不重复的全排列。
**示例 1:**
```
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
```
**示例 2:**
```
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
```
**提示:**
* `1 <= nums.length <= 8`
* `-10 <= nums[i] <= 10`
### 解题步骤
这一题是上一题的升级版,区别是nums中可能有重复元素
那么我们需要多做一个树层去重
这个去重逻辑之前也用过
就是要先对nums数组做个排序,方便确认选择数字是否之前就存在
同时在树层间确认used\[i-1\]==false
即前一个元素是否在这一层被使用过,false是因为用完回溯了
> if(i\>0 \&\& nums\[i\]==nums\[i-1\] \&\& used\[i-1\]==false){
>
> continue;
>
> }
树层重复解决完也不能忘记纵向间也要保持去重逻辑
一个数字不能取两遍,还是需要used数组实现
> if(used\[i\]!=true){
>
> path.push_back(nums\[i\]);
>
> used\[i\]=true;
>
> backtracking(nums,used);
>
> used\[i\]=false;
>
> path.pop_back();
>
> }
其它保持不变,完整代码在下方!
### code
```cpp
class Solution {
public:
vector path;
vector> result;
void backtracking(vector& nums,vector& used){
if(path.size()==nums.size()){
result.push_back(path);
return;
}
for(int i=0;i0 && nums[i]==nums[i-1] && used[i-1]==false){
continue;
}
if(used[i]!=true){
path.push_back(nums[i]);
used[i]=true;
backtracking(nums,used);
used[i]=false;
path.pop_back();
}
}
}
vector> permuteUnique(vector& nums) {
vector used(nums.size(),false);
sort(nums.begin(),nums.end());
backtracking(nums,used);
return result;
}
};
```
*** ** * ** ***
## 题目三:N皇后
### 问题描述
[51. N 皇后 - 力扣(LeetCode)](https://leetcode.cn/problems/n-queens/description/ "51. N 皇后 - 力扣(LeetCode)")
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
**n 皇后问题** 研究的是如何将 `n` 个皇后放置在 `n×n` 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 `n` ,返回所有不同的 **n皇后问题** 的解决方案。
每一种解法包含一个不同的 **n 皇后问题** 的棋子放置方案,该方案中 `'Q'` 和 `'.'` 分别代表了皇后和空位。

### 解题步骤
这一题与之前所有回溯算法的区别在于它是一个二维的
但实际上我们的解题思路还是类似
结果改为用vector\\> result存储(实际上相当于3维,string也算一个维度)
每一个答案是这个棋盘的皇后摆放情况,那么这个就是我们的过程量
原来用path存放,此处为了方便解题我们可以设置为vector\ chessboard
这里需要注意不能初始化为
`vector chessboard(n,".");`
这是创建一个包含`n`个元素的`vector`,每个元素是一个`string`。
每个`string`被初始化为`"."`(即长度为1的字符串,内容是`'.'`)。
因此,`chessboard`是一个`n`行的棋盘,但每行只有1个字符`'.'`。
例如,`n=4`时:
chessboard = {
".",
".",
".",
"."
};
这与我们想要的
chessboard = {
"....",
"....",
"....",
"...."
};
完全不符,所以需要定义棋盘并将其初始化为:
> std::vector\ chessboard(n,std::string(n,'.'));
当然也可以定义为vector\\>,该版本代码附在最后
下面就是使用回溯三部曲,写出主要代码
#### 1.明确参数及返回值
result数组被定义为全部变量,故依旧无需返回值
chessboard作为我们要操作的过程量肯定是参数的一员
同时n作为棋盘大小是我们的边界
那么还需要一个索引指向处理的层数,故使用 int row
这样既可以通过层数了解,定位处理的位置
也可以作为终止条件的参考
> void backtracking(vector\\>\& chessboard,int n,int row)
#### **2.确定终止条件**
我们需要填好整个棋盘才算做一个答案,故结果在叶子结点处取到
也就是row指向最后一层n
> if(row==n){
>
> result.push_back(chessboard);
>
> return;
>
> }
#### **3.单层遍历操作**
在这个部分我们要遍历当前行所有格子,把合法的皇后放进棋盘,
并递归调用函数填满整个棋盘
每次调用后需要回溯
> for(int col=0;col\
> if(isValid....){//检查函数等会再写!
>
> chessboard\[row\]\[col\]='Q';
>
> backtracking(chessboard,n,row+1);
>
> chessboard\[row\]\[col\]='.';
>
> }
>
> }
#### **4.判断合法性函数**
按照规则,我们要确保放入皇后的位置正确,
那么需要用到的参数肯定有当前位置的row和col,棋盘内容chessboard,棋盘大小n
返回值应该是bool型的
那么函数返回值和参数列表应该为
> bool isValid(vector\\& chessboard,int n,int row,int col)
在函数中我们需要进行三个检查
1)列间检查
确保每一列只有一个皇后,
那么需要遍历当前行之前的所有行,去查看\[col\]这个位置是否有Q
> for(int i=0;i\
> if(chessboard\[i\]\[col\]=='Q'){
>
> return false;
>
> }
>
> }
2)45度角检查
确保左上斜线处不存在Q
映照棋盘关系,左上处坐标就是当前格横坐标减一(row-1),纵坐标减一(col-1)
需要一直往左上查找所以不断减减,直到边界
> for(int i=row-1, j=col-1; i\>=0\&\&j\>=0; i--, j--){
>
> if(chessboard\[i\]\[j\]=='Q')
>
> return false;
>
> }
3)135度角检查
原理和上面的类似,不过135度指右上斜线
> for(int i = row - 1, j = col + 1; i \>= 0 \&\& j \< n; i--, j++) {//右边上面一个!
>
> if (chessboard\[i\]\[j\] == 'Q') {
>
> return false;
>
> }
>
> }
都没有违反规则则返回true
然后需要在backtracking函数中正确传参调用即可
完整代码如下!
### code
**vector\版**
```cpp
class Solution {
public:
vector> result;
bool isValid( vector& chessboard, int n,int row, int col) {
// 检查列
for (int i = 0; i < row; i++) { //该列在当前行之前是否出现过Q
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查 45度角是否有皇后
for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {//就是左边上面一个!
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查 135度角是否有皇后
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {//右边上面一个!
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
void backtracking(vector& chessboard,int n,int row){
if(row==n){
result.push_back(chessboard);
return;
}
for(int col=0;col> solveNQueens(int n) {
//vector chessboard(n,".");
std::vector chessboard(n, std::string(n, '.'));
backtracking(chessboard,n,0);
return result;
}
};
```
**vector\\> chessboard版**
```cpp
class Solution {
public:
vector> result;
bool isValid(vector>& chessboard, int n, int row, int col) {
for (int i = 0; i < row; i++) {
if (chessboard[i][col] == 'Q') return false;
}
for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q') return false;
}
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chessboard[i][j] == 'Q') return false;
}
return true;
}
void backtracking(vector>& chessboard, int n, int row) {
if (row == n) {
vector board;
for (const auto& row : chessboard) {
board.push_back(string(row.begin(), row.end()));
}
result.push_back(board);
return;
}
for (int col = 0; col < n; col++) {
if (isValid(chessboard, n, row, col)) {
chessboard[row][col] = 'Q';
backtracking(chessboard, n, row + 1);
chessboard[row][col] = '.';
}
}
}
vector> solveNQueens(int n) {
vector> chessboard(n, vector(n, '.'));
backtracking(chessboard, n, 0);
return result;
}
};
```