回溯算法简单理解

leecode每日一题

回溯算法是一种通过试错来解决问题的算法,当发现当前路径无法得到正确解时,会回溯到上一步尝试其他可能。它特别适合解决 组合问题、排列问题、子集问题、棋盘类问题 等。以下是详细解析和C++实现:


一、回溯算法核心思想

“选择 → 探索 → 撤销” 的循环过程:

  1. 路径:已做出的选择
  2. 选择列表:当前可以做的选择
  3. 结束条件:到达决策树底层时的终止条件

二、算法框架模板

void backtrack(路径, 选择列表) {
    if (满足结束条件) {
        记录结果;
        return;
    }
    
    for (选择 : 选择列表) {
        做选择;
        backtrack(新路径, 新选择列表);
        撤销选择;
    }
}

三、经典问题示例

1. 全排列问题(无重复数字)

问题:给定数组 [1,2,3],返回所有可能的排列。

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> res;
    vector<int> path;
    vector<bool> used(nums.size(), false);
    backtrack(nums, path, used, res);
    return res;
}

void backtrack(vector<int>& nums, vector<int>& path, vector<bool>& used, vector<vector<int>>& res) {
    if (path.size() == nums.size()) {
        res.push_back(path);
        return;
    }
    
    for (int i = 0; i < nums.size(); ++i) {
        if (used[i]) continue;  // 剪枝:已使用的元素跳过
        used[i] = true;         // 做选择
        path.push_back(nums[i]);
        backtrack(nums, path, used, res);
        path.pop_back();        // 撤销选择
        used[i] = false;
    }
}

2. 组合问题(无重复元素,可重复选取)

问题:从 [2,3,5] 中选和为 8 的组合,如 [2,2,2,2]。

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    vector<vector<int>> res;
    vector<int> path;
    sort(candidates.begin(), candidates.end());  // 排序以方便剪枝
    backtrack(candidates, target, 0, path, res);
    return res;
}

void backtrack(vector<int>& nums, int remain, int start, vector<int>& path, vector<vector<int>>& res) {
    if (remain < 0) return;
    if (remain == 0) {
        res.push_back(path);
        return;
    }
    
    for (int i = start; i < nums.size(); ++i) {
        if (nums[i] > remain) break;  // 剪枝:后续元素更大,无需尝试
        path.push_back(nums[i]);
        backtrack(nums, remain - nums[i], i, path, res); // 允许重复,仍从i开始
        path.pop_back();
    }
}

3. N皇后问题

问题:在N×N棋盘放置皇后,使其不能互相攻击。

vector<vector<string>> solveNQueens(int 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 == board.size()) {
        res.push_back(board);
        return;
    }
    
    int n = board[row].size();
    for (int col = 0; col < n; ++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) {
    // 检查列
    for (int i = 0; i < row; ++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<board.size(); --i, ++j)
        if (board[i][j] == 'Q') return false;
    return true;
}

四、关键技巧

  1. 剪枝优化:通过排序提前终止无效分支(如组合问题)
  2. 去重处理:使用排序 + i > start && nums[i] == nums[i-1] 跳过重复元素
  3. 状态保存:使用used数组或索引start避免重复使用元素

五、时间复杂度分析

回溯算法通常时间复杂度较高(指数级),但通过剪枝可显著优化。例如:

  • 全排列问题:O(n×n!)
  • 组合问题:O(2^n)

六、练习题目

  1. 全排列II(含重复数字)
  2. 子集(无重复元素)
  3. 组合总和II(每个数字只能用一次)
  4. 解数独

掌握回溯算法需要理解其递归树结构,并熟练应用剪枝技巧来优化效率。建议从模板入手,逐步理解不同问题的变种处理方式。

AC代码

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        // 对数组进行排序,方便后续去重
        sort(nums.begin(), nums.end());
        vector<int> combination;
        vector<vector<int>> combinations;
        // 调用回溯函数生成子集
        backtrack(0, nums, combination, combinations);
        return combinations;
    }

    void backtrack(int index, const vector<int>& nums, vector<int>& combination, vector<vector<int>>& combinations) {
        // 将当前组合加入结果集
        combinations.push_back(combination);
        for (int i = index; i < (int)nums.size(); ++i) {
            // 跳过重复元素
            if (i > index && nums[i] == nums[i - 1]) continue;
            // 选择当前元素
            combination.push_back(nums[i]);
            // 递归生成子集
            backtrack(i + 1, nums, combination, combinations);
            // 回溯,撤销选择
            combination.pop_back();
        }
    }
};

你可能感兴趣的:(刷题,c++,算法,c++)