非递减子序列 leetcode 491
树层去重,树枝不需要去重。因为同一层如果出现相同的数,前面的数已经把所有情况取完了。
写法一:unordered_set去重 O(n*2^n) 递归树时间复杂度2^n,在每层中set去重时间复杂度为n。
每一层递归都重新创建一个set记录本层出现过的元素,所以在递归之后set不需要进行重塑。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>& nums,int startIndex){
if(path.size()>1) res.push_back(path);
unordered_set<int> set;
if(startIndex>=nums.size()) return;
for(int i=startIndex;i<nums.size();i++){
if(!path.empty()&&nums[i]<path.back()||set.find(nums[i])!=set.end()) continue;
path.push_back(nums[i]);
set.insert(nums[i]);
backtracking(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
res.clear();
path.clear();
backtracking(nums,0);
return res;
}
};
写法二:数组去重
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>& nums,int startIndex){
if(path.size()>1) res.push_back(path);
int used[201]={0};
for(int i=startIndex;i<nums.size();i++){
if(!path.empty()&&nums[i]<path.back()
||used[nums[i]+100]==1) continue;
path.push_back(nums[i]);
used[nums[i]+100]=1;
backtracking(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
res.clear();
path.clear();
backtracking(nums,0);
return res;
}
};
全排列 leetcode 46
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>& nums,vector<bool>& used){
if(path.size()==nums.size()){
res.push_back(path);
return;
}
for(int i=0;i<nums.size();i++){
if(used[i]==true) continue;
used[i]=true;
path.push_back(nums[i]);
backtracking(nums,used);
path.pop_back();
used[i]=false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
res.clear();
path.clear();
vector<bool> used(nums.size(),false);
backtracking(nums,used);
return res;
}
};
出现的错误
递归函数开始后在递归结束的if条件中没有写return;
全排列Ⅱ leetcode 47
与全排列上一题相比多加一个树层去重,因为数组中会出现重复元素,所以先排序,之后树层去重,相邻的相同元素的两条树枝,其中上一条树枝中已经包含了所有情况,减去剩余的一条树枝。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>& nums,vector<bool>& used){
if(path.size()==nums.size()){
res.push_back(path);
return;
}
for(int i=0;i<nums.size();i++){
if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false //树层去重
||used[i]==true) continue; //树枝去重
path.push_back(nums[i]);
used[i]=true;
backtracking(nums,used);
path.pop_back();
used[i]=false;
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
res.clear();
path.clear();
sort(nums.begin(),nums.end());
vector<bool> used(nums.size(),false);
backtracking(nums,used);
return res;
}
};
nums[i]==nums[i-1]&&used[i-1]==false 同一层树层使用过,经过回溯为false。
nums[i]==nums[i-1]&&used[i-1]==true 同一条树枝使用过。
重新安排行程 leetcode 332
解析
1.使用unordered_map<出发机场,map<到达机场,tickets中包含此到达机场的航班次数>>,其中包含此到达机场的航班次数,就说明该机场还可以飞。
题目要求按字母大小排序,那么使用map存放到达机场的时候就会自己按照字母顺序排序。
for(const vector<string>& vec:tickets){
targets[vec[0]][vec[1]]++;
}
范围for循环初始化targets,记录出发机场以及到达机场,++记录到达机场次数。
2.回溯递归函数的返回值为什么是bool类型?
如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。
3.for(pair<const string,int>& target_map:targets[res[res.size()-1]])
递归中的范围for循环用于遍历,当前结果集中最后一个元素作为出发机场的所有到达机场次数,如果到达机场次数>0表示对应到达机场还没有用完,直接放入结果集,向下递归以到达机场作为出发机场继续for循环遍历下一层。
测试用例
[["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
首先将所有车票初始化tickets之后,map中到达机场value的值分别为,JFK=1,SFO=2,ATL=2
主函数中会先将JFK插入到res结果集中,进入递归for循环后,查询JFK作为出发机场的到达机场,分别是SFO和ATL,因为map是经过排序的,所以先遍历ATL(ATL=1),再以ATL作为出发机场遍历到达机场,分别是JFK和SFO,因为map有序所以先遍历JFK(JFK=0),再以JFK作为出发机场遍历到达机场,只有SFO(SFO=1),再以SFO作为出发机场遍历到达机场,只有ATL(ATL=0),再以ATL作为出发机场遍历到达机场,分别是JFK和SFO,因为map有序所以应该先选JFK但是JFK作为到达机场对应value已经为0,所以选择SFO(SFO=0),此时res中JFK-ATL-JFK-SFO-ATL-SFO符合递归结束条件。如下图(虚线地方我认为不会遍历到):
class Solution {
public:
unordered_map<string,map<string,int>> targets;
bool backtracking(int ticketNum,vector<string>& res){
if(res.size()==ticketNum+1) return true;
for(pair<const string,int>& target_map:targets[res[res.size()-1]]){
if(target_map.second>0){
res.push_back(target_map.first);
target_map.second--;
if(backtracking(ticketNum,res)) return true;
res.pop_back();
target_map.second++;
}
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
targets.clear();
vector<string> res;
for(const vector<string>& vec:tickets){
targets[vec[0]][vec[1]]++;
}
res.push_back("JFK");
backtracking(tickets.size(),res);
return res;
}
};
总结
targets[vec[0]][vec[1]]++;
其中++
符号是对targets中嵌套的map中对应键的值进行自增操作。
具体解释一下:
targets[vec[0]],首先,使用vec[0]作为外层unordered_map的键来查找或创建一个键值对。如果这个键值对不存在,则会创建一个新的map<string,int>并将其与vec[0]键关联起来。
vec[1],接着,使用vec[1]作为内层map的键来查找或创建一个键值对。如果这个键值对不存在,则会创建一个新的int值并将其与vec[1]键关联起来。
++:最后,对内层map中vec[1]键对应的值执行自增操作。
出现的错误
递归函数中for循环遍历中第一个if语句条件写成了target_map.second==1,同一个机场可能是多个机票的到达机场。
N皇后 leetcode 51
class Solution {
private:
vector<vector<string>> res;
void backtracking(int n,int row,vector<string>& chess){
if(n==row){
res.push_back(chess);
return;
}
for(int i=0;i<n;i++){
if(isVaild(row,i,chess,n)){
chess[row][i]='Q';
backtracking(n,row+1,chess);
chess[row][i]='.';
}
}
}
bool isVaild(int row,int col,vector<string>& chess,int n){
for(int i=0;i<row;i++){
if(chess[i][col]=='Q') return false;
}
for(int i=0;i<col;i++){
if(chess[row][i]=='Q') return false;
}
for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){
if(chess[i][j]=='Q') return false;
}
for(int i=row-1,j=col+1;i>=0&&j>=0;i--,j++){
if(chess[i][j]=='Q') return false;
}
return true;
}
public:
vector<vector<string>> solveNQueens(int n) {
res.clear();
vector<string> chess(n,string(n,'.'));
backtracking(n,0,chess);
return res;
}
};
总结
1.其中isValid函数中对棋盘行的检查可以去掉,因为递归函数中控制每一行只放一个皇后。
2.string(n,'.'); 创建一个字符串长度为n且每一个字符都是“.”。
解数独 leetcode 37
如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。
class Solution {
private:
bool backtracking(vector<vector<char>>& board){
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
if(board[i][j]=='.'){
for(char k='1';k<='9';k++){
if(isValid(i,j,k,board)){
board[i][j]=k;
bool res=backtracking(board);
if(res) return true;
board[i][j]='.';
}
}
return false;
}
}
}
return true;
}
bool isValid(int row,int col,int val,vector<vector<char>>& board){
for(int i=0;i<board.size();i++){
if(board[i][col]==val) return false; //排查列有无相同元素
}
for(int i=0;i<board.size();i++){
if(board[row][i]==val) return false; //排查行有无相同元素
}
//找到当前3×3格子第一行第一列在整个格子中的位置,排查当前3×3格子有无重复元素
int startRow=(row/3)*3;
int startCol=(col/3)*3;
for(int i=startRow;i<startRow+3;i++){
for(int j=startCol;j<startCol+3;j++){
if(board[i][j]==val) return false;
}
}
return true;
}
public:
void solveSudoku(vector<vector<char>>& board) {
backtracking(board);
return;
}
};