Skip to content
Rain Hu's Workspace
Go back

[Algo] 2-4. 回溯法 Backtracking

Rain Hu

一、回溯法

二、回溯法的框架

vector<Node*> path;
vector<vector<Node*>> res;
void backtrack(Node* root) {
    if (terminate_condition) {  // 當終止條件時
        res.push_back(path);    // 將路徑加入結果
        return;                 // 治原路徑返回
    }
    for (auto& next : root->children) {
        path.push_back(next);   // 將選擇加入路徑
        backtrack(next);
        path.pop_back();        // 從路徑中撤銷選擇
    }
}
void backtrack(vector<int>& nums, vector<bool>& visited, vector<int>& path) {
    if (path.size() == nums.size()) {
        res.push_back(path);
        return;
    }
    for (int i = 0; i < nums.size(); i++) {
        if (visited[i]) continue;
        visited[i] = true;
        path.push_back(nums[i]);
        backtrack(nums, visited, path);
        visited[i] = false;
        path.pop_back();
    }
}

三、例題

1. [Leetcode] 51. N-Queens

int sz;
vector<vector<string>> solveNQueens(int n) {
    sz = n;
    vector<vector<string>> res;
    vector<string> board(n, string(n, '.'));
    backtrack(board, 0, res);
    return res;
}

void backtrack(vector<string>& board, int row, vector<vector<string>>& res){
    if (row == sz){         // 終止條件:走完 n 行
        res.push_back(board);
        return;
    }
    for (int col = 0; col < sz; col++){
        if (!isValid(board, row, col)) continue;
        board[row][col] = 'Q';      // 放皇后
        backtrack(board, row+1, res);
        board[row][col] = '.';      // 撤銷皇后
    }
}

// 直行、橫列、斜線都不能出線皇后
bool isValid(vector<string>& board, int& row, int& col){
    if (row == sz) return true;
    for (int i = row - 1; i >= 0; i--)
        if (board[i][col] == 'Q') return false;
    for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
        if (board[i][j] == 'Q') return false;
    }
    for (int i = row - 1, j = col + 1; i >= 0 && j < sz; i--, j++){
        if (board[i][j] == 'Q') return false;
    }
    return true;
}

2. [Leetcode] 797. All Paths From Source to Target

vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
    vector<vector<int>> res;
    vector<int> path;
    path.push_back(0);      // 站在起點 0
    backtrack(graph, res, path, 0, -1);
    return res;
}
void backtrack(vector<vector<int>>& graph, vector<vector<int>>& res, vector<int>& path, int curr, int last) {
    if (curr == graph.size()-1) {       // 到達終點 n-1
        res.push_back(path);
        return;
    }
    for (const auto& next : graph[curr]) {
        // if (last == next) continue;      // 若是 directed 或是 cyclic graph,需要避免走回頭路
        path.push_back(next);               // 做選擇
        backtrack(graph, res, path, next, curr);
        path.pop_back();                    // 做撤銷
    }
}

3. [Leetcode] 980. Unique Path III

int res;
int uniquePathsIII(vector<vector<int>>& grid) {
    res = 0;
    int m = grid.size(), n = grid[0].size();
    // 先記錄機器人的起點與終點
    pair<int,int> start, end;
    // 記錄機器人所需走多少步(選擇的次數): 棋格數-障礙-1
    int left = m*n;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid[i][j] == 1) {
                start = {i, j};
                left--;
            } else if (grid[i][j] == -1) {
                left--;
            }
        }
    }
    backtrack(grid, start.first, start.second, left);
    return res;
}
int dir[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
void backtrack(vector<vector<int>>& grid, int row, int col, int left) {
    // 超出棋格、或是已經走過
    if (row < 0 || row >= grid.size() || col < 0 || col >= grid[0].size() || grid[row][col] == -1) return;
    // 終止條件:到達目標且每一個空白棋格都走完(left == 0)
    if (grid[row][col] == 2 && left == 0) {
        res++;
        return;
    }
    int tmp = grid[row][col];       // 做選擇
    grid[row][col] = -1;            // 做標記,代表已走過
    for (const auto& d : dir) {
        backtrack(grid, row+d[0], col+d[1], left-1);
    }
    grid[row][col] = tmp;           // 做撤銷
}


Share this post on:

Previous
[Life] 原子習慣
Next
[Algo] 2-3. 分治法 Divide and Conquer