Leetcode刷题笔记Day13:回溯Ⅲ总结篇

83 阅读4分钟

子集II

  • 力扣题目链接
  • 这道题是子集的升级版,可包含重复元素数组的子集,涉及最终解集的去重
  • 按照子集的写法写一遍,去重的逻辑是排序+used数组或startIndex
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int start){
    result.push_back(path);
    if(start>=nums.size()) return;
    for(int i=start; i<nums.size(); i++){
        // 树层去重
        if(i>start && nums[i]==nums[i-1]) continue;
        path.push_back(nums[i]);
        backtracking(nums, i+1);
        path.pop_back();
    }
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    if(nums.empty()) return result;
    sort(nums.begin(), nums.end());
    backtracking(nums, 0);
    return result;
}

递增子序列

  • 使用unordered_set对树层去重:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int start){
    if(path.size()>1) result.push_back(path);
    if(start>=nums.size()) return;
    unordered_set<int> u; // 使用set对本层元素进行去重
    for(int i=start; i<nums.size(); i++){
        if((!path.empty() && nums[i]<path.back())
           || u.find(nums[i])!=u.end()) continue;
        u.insert(nums[i]);
        path.push_back(nums[i]);
        backtracking(nums, i+1);
        path.pop_back();
    }
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
    backtracking(nums, 0);
    return result;
}
  • 优化后使用数组记录:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int start){
    if(path.size()>1) result.push_back(path);
    if(start>=nums.size()) return;
    int used[201]={0}; // 使用used数组对本层元素进行去重
    for(int i=start; i<nums.size(); i++){
        if((!path.empty() && nums[i]<path.back())
           || used[nums[i]+100]==1) continue;
        used[nums[i]+100]=1;
        path.push_back(nums[i]);
        backtracking(nums, i+1);
        path.pop_back();
    }
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
    backtracking(nums, 0);
    return result;
}

全排列

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, vector<bool>& used){
    if(path.size()==nums.size()) {
        result.push_back(path); return;
    }
    for(int i=0; i<nums.size(); i++){
        if(used[i]) continue;
        path.push_back(nums[i]);
        used[i]=1;
        backtracking(nums, used);
        path.pop_back();
        used[i]=0;
    }
}
vector<vector<int>> permute(vector<int>& nums) {
    vector<bool> used(nums.size(), false);
    backtracking(nums, used);
    return result;
}

全排列 II

  • 力扣题目链接
  • 可包含重复数字的,同样的去重方法(排序的used数组就有双重用处了)
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, vector<bool>& used){
    if(path.size()==nums.size()) {
        result.push_back(path); return;
    }
    for(int i=0; i<nums.size(); i++){
        // 树层去重
        if(i>0 && nums[i]==nums[i-1] && used[i-1]==false)
            continue;
        if(!used[i]){
            path.push_back(nums[i]);
            used[i]=true;
            backtracking(nums, used);
            path.pop_back();
            used[i]=false;
        }
    }
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    vector<bool> used(nums.size(), false);
    backtracking(nums, used);
    return result;
}
  • used[i - 1] == true也可以,但效率不高(树枝去重<树层去重)

重新安排行程

  • 力扣题目链接
  • 从本题往下3题,就是难题了,题目涉及的内容非常综合
  • 这题是图论额外拓展题,涉及深搜,但是深搜的本质是递归!
  • 使用unordered_map记录机票的图结构
// unordered_map<出发机场, map<到达机场, 航班次数>> targets
unordered_map<string, map<string, int>> targets;
bool backtracking(int ticketNum, vector<string>& result){
    // ticketNum表示有多少个航班
    if(result.size()==ticketNum+1) return true;
    for(pair<const string, int>& target : targets[result[result.size()-1]]){
        if(target.second>0){ // 记录机票是否用过
            result.push_back(target.first);
            target.second--;
            if(backtracking(ticketNum, result)) return true;
            target.second++;
            result.pop_back();
        }
    }
    return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
    for(const vector<string>& vec : tickets)
        // 目的是保证每张机票只用一次
        targets[vec[0]][vec[1]]++; // 记录映射关系
    vector<string> result;
    result.push_back("JFK"); // 起始机场
    backtracking(tickets.size(), result);
    return result;
}
  • 一定要加上引用即 & target,因为后面有对target.second做减减操作,如果没有引用,单纯复制,这个结果就没记录下来,那最后的结果就不对了。加上引用之后,就必须在string前面加上const,因为map中的key 是不可修改了,这就是语法规定了。

N皇后

vector<vector<string>> result;
// n 为输入的棋盘大小
// row 是当前递归到棋盘的第几行
// 之所以chessboard设置为参数,是因为不知道n的大小
void backtracking(int n, int row, vector<string>& chessboard){
    if(row==n) {result.push_back(chessboard); return;}
    for(int col=0; col<n; col++)
        if(isValid(row, col, chessboard, n)){
            chessboard[row][col]='Q';
            backtracking(n, row+1, chessboard);
            chessboard[row][col]='.'; // 回溯
        }
}
bool isValid(int row, int col, vector<string>& chessboard, int n){
    // 检查列
    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;
}
vector<vector<string>> solveNQueens(int n) {
    // 注意棋盘的初始化方法
    vector<string> chessboard(n, string(n, '.'));
    backtracking(n, 0, chessboard);
    return result;
}

解数独

// 二维递归回溯
bool backtracking(vector<vector<char>>& board){
    for(int i=0; i<9; i++)
        for(int j=0; j<9; j++){
            if(board[i][j]!='.') continue;
            for(char k='1'; k<='9'; k++)
                if(isValid(board, i, j, k)){
                    board[i][j]=k;
                    // 因为返回值是bool
                    if(backtracking(board)) return true;
                    board[i][j]='.';
                }
            return false; // 试完9个数都不行
        }
    return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
}
bool isValid(vector<vector<char>>& board, int row, int col, char num){
    // 行
    for(int j=0; j<9; j++)
        if(board[row][j]==num) return false;
    // 列
    for(int i=0; i<9; i++)
        if(board[i][col]==num) return false;
    // 九宫格
    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]==num) return false;
    return true;
}
void solveSudoku(vector<vector<char>>& board) {
    backtracking(board);
}

总结

  • 递归和回溯是非常重要的思维方式,相当于半只脚踏入了DP的门槛,接下来让我们来迎接图论和DP的暴击!

img

参考资料

[1] 代码随想录

[2] Leetcode题解