回溯算法#

组合、组合总和、非递减子序列、全排列、N 皇后、解数独等回溯模板。

回溯算法#

组合 k 个数#

// n 个数字的组合问题,其实等价于 n 个数字的全连接图问题
// 每个数字都可以作为起点,每个数字都可以作为终点,但是两条路径不能重合
void dfs(vector<vector<int>>& paths, vector<int>& path, int start, int n, int k) {
    if (path.size() == k) {
        paths.push_back(path);
        return;
    }

    // for (int i = start; i <= n - (k - path.size()) + 1; i++) { // 剪枝优化
    for (int i = start; i <= n; i++) {
        path.push_back(i);
        dfs(paths, path, i + 1, n, k); // dfs(i+1) 表示不可重复选
        path.pop_back();               // 回溯
    }

    return;
}

vector<vector<int>> combine(int n, int k) {
    vector<vector<int>> paths;
    vector<int> path;
    dfs(paths, path, 1, n, k);
    return paths;
}

组合总和(可重复选)#

void dfs(vector<vector<int>>& paths, vector<int>& path, int start, vector<int>& candidates,
         int target) {
    if (target < 0) {
        return;
    }

    if (target == 0) {
        paths.push_back(path);
        return;
    }

    for (int i = start; i < candidates.size(); i++) {
        path.push_back(candidates[i]);
        dfs(paths, path, i, candidates, target - candidates[i]); // dfs(i) 表示可重复选
        path.pop_back();
    }
}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    vector<vector<int>> paths;
    vector<int> path;
    dfs(paths, path, 0, candidates, target);
    return paths;
}

非递减子序列(去重)#

// 判断是否为递增序列
bool isValid(vector<int>& path) {
    for (int i = 1; i < path.size(); i++) {
        if (path[i] < path[i - 1]) {
            return false;
        }
    }
    return true;
}

void dfs(vector<vector<int>>& paths, vector<int>& path, int start, vector<int>& nums) {
    if (path.size() > 1) {
        if (isValid(path)) {
            paths.push_back(path);
        }
    }

    unordered_set<int> used_set; // 对本层应用去重
    for (int i = start; i < nums.size(); i++) {
        if (used_set.find(nums[i]) != used_set.end()) { // 已经使用过 nums[i] 了
            continue;
        }

        path.push_back(nums[i]);
        used_set.insert(nums[i]);
        dfs(paths, path, i + 1, nums);
        // used_set.erase(nums[i]); // 不能解开注释
        path.pop_back();
    }
}

vector<vector<int>> findSubsequences(vector<int>& nums) {
    vector<vector<int>> paths;
    vector<int> path;
    dfs(paths, path, 0, nums);
    return paths;
}

全排列(不含重复元素)#

void dfs(vector<vector<int>>& paths, vector<int>& path, vector<int>& nums,
         vector<bool>& used) {
    if (path.size() == nums.size()) {
        paths.push_back(path);
        return;
    }

    for (int i = 0; i < nums.size(); i++) {
        if (used[i]) {
            continue;
        }
        path.push_back(nums[i]);
        used[i] = true;
        dfs(paths, path, nums, used);
        used[i] = false;
        path.pop_back();
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> paths;
    vector<int> path;
    vector<bool> used(nums.size(), false);
    dfs(paths, path, nums, used);
    return paths;
}
// 比如:模拟从黑箱子中取球的过程(有放回)
// 回溯不同于动态规划,动态规划有公式可循
// 用 arr 表示原始数组,用 used 剪枝优化
// 用 i == arr.length 判断递归是否终止
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();

void dfs(int[] arr, boolean[] used, int i) {
    if (i == arr.length) {
        // 注意,深拷贝
        ans.add(new ArrayList<>(path));
        return;
    }
    // 每次都向 path 的第 j 个位置推送不同数字
    for (int j = 0; j < nums.length; j++) {
        if (!used[j]) {
            path.add(nums[j]);
            used[j] = true;
            dfs(nums, used, i + 1);
            used[j] = false; // 撤销原操作
            path.remove(path.size() - 1);
        }
    }
}

全排列(含重复元素)#

void dfs(vector<vector<int>>& paths, vector<int>& path, vector<int>& nums,
         vector<bool>& used) {
    if (path.size() == nums.size()) {
        paths.push_back(path);
        return;
    }

    for (int i = 0; i < nums.size(); i++) {
        if (used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) {
            continue;
        }

        path.push_back(nums[i]);
        used[i] = true;
        dfs(paths, path, nums, used);
        used[i] = false;
        path.pop_back();
    }
}

vector<vector<int>> permuteUnique(vector<int>& nums) {
    vector<vector<int>> paths;
    vector<int> path;
    vector<bool> used(nums.size(), false);
    sort(nums.begin(), nums.end());
    dfs(paths, path, nums, used);
    return paths;
}

N 皇后问题#

void dfs(vector<vector<string>>& paths, vector<string>& path, int row, int n) {
    if (row == n) { // 遍历到最后一行,说明已经找到了一种解法
        paths.push_back(path);
        return;
    }

    // 遍历当前行的每一列,判断当前位置是否可以放置皇后
    for (int col = 0; col < n; col++) {
        // 判断当前位置是否可以放置皇后
        if (path[row][col] == '.') {
            bool flag = true; // flag 为 true 表示当前位置可以放置皇后

            // 判断同一列上是否有皇后
            for (int i = 0; i < row; i++) {
                if (path[i][col] == 'Q') {
                    flag = false;
                    break;
                }
            }

            // 判断同一斜线上是否有皇后
            if (flag) {
                for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
                    if (path[i][j] == 'Q') {
                        flag = false;
                        break;
                    }
                }
            }
            if (flag) {
                for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
                    if (path[i][j] == 'Q') {
                        flag = false;
                        break;
                    }
                }
            }

            // 如果当前位置可以放置皇后,则递归调用 dfs 函数,继续向下一行进行遍历
            if (flag) {
                path[row][col] = 'Q';
                dfs(paths, path, row + 1, n);
                path[row][col] = '.';
            }
        }
    }
}

vector<vector<string>> solveNQueens(int n) {
    vector<vector<string>> paths;
    vector<string> path(n, string(n, '.'));
    dfs(paths, path, 0, n);
    return paths;
}

解数独#

bool isValid(vector<vector<char>>& board, int row, int col, char num) {
    // 检查当前行或列是否有重复的数字
    for (int i = 0; i < 9; i++) {
        if (board[row][i] == num || board[i][col] == num) {
            return false;
        }
    }

    // 检查 3 x 3 宫格内是否有重复的数字
    int startRow = row - row % 3, startCol = col - col % 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 dfs(vector<vector<char>>& temp, vector<vector<char>>& board, int row, int col) {
    if (row == 9) {
        temp = board;
        return;
    }

    if (col == 9) {
        dfs(temp, board, row + 1, 0);
        return;
    }

    if (board[row][col] != '.') {
        dfs(temp, board, row, col + 1);
        return;
    }

    for (char num = '1'; num <= '9'; num++) {
        if (isValid(board, row, col, num)) {
            board[row][col] = num;
            dfs(temp, board, row, col + 1);
            board[row][col] = '.'; // 回溯
        }
    }
}

void solveSudoku(vector<vector<char>>& board) {
    // 回溯后 board 会恢复原样,因此需要创建一个临时变量保存 board 的状态
    vector<vector<char>> temp = board;
    dfs(temp, board, 0, 0);
    board = temp;
}