回溯算法 典型习题

发布时间:2023年12月24日
vector<vector<int>> res;
vector<int> path;

void dfs() {
    if (递归终止条件){
        res.push_back(path);
        return;
    }
    // 递归方向
    for (xxx) {
        path.push_back(val);
        dfs();
        path.pop_back();
    }
}

1.涉及枚举

2.不确定 for 循环的次数

总结

枚举各种可能的情况。

0.直接枚举子集

1.约束条件是子集中数字的和 39

2.约束条件是子集的大小 77 46 47

3.约束条件是1 2两者的结合 2161

4.约束条件是集合数 + sum 93 698

5.去重:同层删去相同的递归起点

6.约束条件是 子集中数的大小关系 491

7.前一个情况可能是后一个情况的约束 51

77

第一层可以选 1 2 3 4

第二层可以选 234 134 ...

需要 path 存储选择的路径。需要 index 作为元素下标。

class Solution {
public:

    // 储存当前路径
    vector<int> path;
    vector<vector<int>> res;

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

    void dfs(int index, int n, int k) {
        // 比如 k = 2, n = 4, index = 4
        // 不足以构成数组,要提前结束
        if (path.size() + (n - index + 1) < k) return;

        // 如果路径的长度是 k,那么把这个路径加入到结果数组
        if (path.size() == k) {
            res.push_back(path);
            return;
        }

        // 否则的话,从 index 开始回溯
        for (int i = index; i <= n; ++i) {
            path.push_back(i);
            dfs(i + 1, n, k);
            path.pop_back();
        }

    }
};

39

target = 7

2 2 3/ 2 3 2

3 4

递归树:

s1 2 3 6 7

s2 2367 2367 ...

s3 2367 ...

这一次选了2,下一次从>=2开始选

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        //先排序
        sort(candidates.begin(), candidates.end());
        // 从index = 0的位置开始选,一开始 sum = 0
        dfs(0, 0, candidates, target);
        return res;
    }

    void dfs(int index, int sum, vector<int>& candidates, int target) {

        if (sum >= target) {
            if (sum == target) {
                res.push_back(path);
            }
            return;
        }
        
        // 这次选了 index 下次从 index 开始选
        for (int i = index; i <= candidates.size() - 1; ++i) {
            if (sum + candidates[i] > target) return;
            path.push_back(candidates[i]);
            // 更新 sum
            dfs(i, sum + candidates[i], candidates, target);
            path.pop_back();
        }

    }
};

40 同层去重

和39的区别是不能重复

# 模板
class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        dfs(0, 0, candidates, target);
        return res;
    }

    void dfs(int index, int sum, vector<int>& candidates, int target) {
        if (sum >= target) {
            if (sum == target) {
                res.push_back(path);
            }
            return;
        }

        // 循环 + 递归
        for () {
        
        }
    }
};

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        dfs(0, 0, candidates, target);
        return res;
    }

    void dfs(int index, int sum, vector<int>& candidates, int target) {
        if (sum >= target) {
            if (sum == target) {
                res.push_back(path);
            }
            return;
        }

        // 同层去重
        // 递归起点都是2,那么后面可以不必递归了
        unordered_set<int> occ;
        for (int i = index; i <= candidates.size() - 1; ++i) {
            if (occ.find(candidates[i]) != occ.end()) {
                continue;
            }
            occ.insert(candidates[i]);
            path.push_back(candidates[i]);
            dfs(i + 1, sum + candidates[i], candidates, target);
            path.pop_back();
        }
    }
};

216 固定数据集

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    vector<vector<int>> combinationSum3(int k, int n) {
        dfs(1, 0, k, n);
        return res;
    }

    void dfs(int index, int sum, int k, int n) {
        if (sum >= n) {
            // 选 k 个数 和为 n
            if (sum == n && path.size() == k) {
                res.push_back(path);
            }
        }

        for (int i = index; i <= 9; ++i) {
            if (sum + i > n) {
                return;
            }
            path.push_back(i);
            dfs(i + 1, sum + i, k, n);
            path.pop_back();
        }
    }
};

93 复原ip地址

遍历字符串长度

根据不同的长度截取子串

class Solution {
public:
    vector<string> res;
    vector<string> segMent;

    vector<string> restoreIpAddresses(string s) {
        segMent.resize(4);
        dfs(0, 0, segMent, s);
        return res;
    }

    bool check(string s) {
        // 要么是 0 要不是 0 开头的
        // 字符串转数字
        return (s[0] != '0' || s == "0") && stoi(s) < 256;
    }
    string toString(vector<string> &segMent) {
        string res;
        for (int i = 0; i < 3; ++i) {
            res += segMent[i];
            res += '.';
        }
        res += segMent[3];
        return res;
    }

    // 
    void dfs(int index, int segId, vector<string> &segMent, string s){
        if (index == s.size() || segId == 4) {
            if (index == s.size() && segId == 4) {
                res.push_back(toString(segMent));
            }
            return;
        }

        for (int i = 1; i <= 3; ++i) {
            if (index +i > s.size()) return;
            string sub;
            // 从 index 开始截取长度为 i
            sub = s.substr(index, i);
            if (check(sub)) {
                segMent[segId] = sub;
                dfs(index + i, segId + 1, segMent, s);
            }
        }
    }
};

78 子集

123

path 初始为空

1 2 3

23 3 /

3/? /?

某 index 数取完就从 index + 1 开始取

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(0, nums);
        return res;
    }

    void dfs(int index, vector<int>& nums) {
        res.push_back(path);
        for (int i = index; i <= nums.size() - 1; ++i) {
            path.push_back(nums[i]);
            dfs(i + 1, nums);
            path.pop_back();
        }

    }
};

491 非递增子序列

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

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

    unordered_set<int> appear;
    void dfs(int index, vector<int>& nums) {
        if (path.size() >= 2) {
            res.push_back(path);
        }
        // 同层去重 [4, 6, 6, 7]
        unordered_set<int> occ;

        for (int i = index; i <= nums.size() - 1; ++i) {
            // 确保当前的递归起点没被遍历过
            if (occ.find(nums[i]) != occ.end()) continue;
            occ.insert(nums[i]);
            // 确保序列一直保持递增
            if (path.size() > 0 && nums[i] < path.back()) continue;

            path.push_back(nums[i]);
            dfs(i + 1, nums);
            path.pop_back();
        }
    }
};

46 全排列

每一次都是从头到尾遍历

class Solution {
public:
    vector<int> path;
    vector<int> used;  // 将 vector<bool> 改为 vector<int>
    vector<vector<int>> res;

    vector<vector<int>> permute(vector<int>& nums) {
        used.resize(nums.size(), 0);  // 初始化 used 向量
        dfs(nums);
        return res;
    }

    void dfs(vector<int>& nums) {
        if (path.size() == nums.size()) {
            res.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); ++i) {
            // 如果数字没被用过则改为被用过,且更新path
            if (!used[i]) {
                used[i] = 1;  // 将 false 改为 1,表示已使用
                path.push_back(nums[i]);
                dfs(nums);
                path.pop_back();
                used[i] = 0;  // 将 true 改为 0,表示未使用
            }
        }
    }
};

47 不重复全排列

和 46 不同的一点是,

[1,1,2] 会出现 [2,1,1] 两次,那么加一个hash去重

class Solution {
public:
    vector<vector<int>> res;
    vector<int> used;
    vector<int> path;

    vector<vector<int>> permuteUnique(vector<int>& nums) {
        used.resize(nums.size(), 0);
        dfs(nums);
        return res;
    }

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

        // 同层去重,去除递归起点相同的同层元素
        unordered_set<int> occ;

        for (int i = 0; i < nums.size() ; ++i) {

            if (!used[i] && (occ.find(nums[i]) == occ.end())) {
                used[i] = 1;  // 将 false 改为 1,表示已使用
                path.push_back(nums[i]);
                dfs(nums);
                path.pop_back();
                used[i] = 0;  // 将 true 改为 0,表示未使用
            }
        }
    }
};

698 k 个等和子集

class Solution {
public:
    vector<int> subs;

    int ave;
    bool canPartitionKSubsets(vector<int>& nums, int k) {
        int sum = 0;
        // 桶
        subs.resize(k,0);

        // 求和
        sum = accumulate(nums.begin(), nums.end(), 0);

        // 是否能被 k 整除
        if (sum % k != 0) {
            return false;
        }

        ave = sum / k;

        // 先装大的
        //sort(nums.begin(), nums.end(), greater());
        sort(nums.begin(), nums.end(), greater());

        // 从 0 号位置开始
        return dfs(0, nums, k);

    }

    bool dfs(int index, vector<int>& nums, int k) {
        // 如果已经遍历完了所有数字
        // 查看每个子集大小
        if (index == nums.size()) {
            for (auto sub : subs) {
                if (sub != ave) {
                    return false;
                }
            }
            return true;
        }

        // 对于同一个元素不要尝试和相同的子集
        unordered_set<int> occ;

        // 核心逻辑
        // 分 k 个桶,依次遍历,更新每个桶中元素的值,再 dfs
        // dfs 时依次选取不同的数字

        // 如果当前的桶再装入一个数字超过 ave
        // 去重
        for (int i = 0; i < k; ++i) {
            if (subs[i] +  nums[index] > ave || occ.find(subs[i]) != occ.end()) continue;
            occ.insert(subs[i]);
            subs[i] += nums[index];
            if (dfs(index + 1, nums, k)) return true;
            subs[i] -= nums[index];
        }
        return false;
    }
};

51 皇后

class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<bool> col(n, false);
        vector<bool> diag1(20, false);
        vector<bool> diag2(20, false);

        vector<int> queens(n, 0);

        dfs(0, col, diag1, diag2, queens, n);

        return res; // 返回结果集
    }

private:
    vector<vector<string>> res; // 存储结果集

    // 深度优先搜索函数
    void dfs(int index, vector<bool>& col, vector<bool>& diag1, vector<bool>& diag2, vector<int>& queens, int n) {
        if (index == n) {
            res.push_back(generate(queens, n));
            return;
        }

        for (int i = 0; i < n; ++i) {
            if (col[i] || diag1[index + i] || diag2[index - i + 9]) continue;
            queens[index] = i;// 当前行第 i 列放置皇后
            col[i] = diag1[index + i] = diag2[index - i + 9] = true;
            dfs(index + 1, col, diag1, diag2, queens, n);
            col[i] = diag1[index + i] = diag2[index - i + 9] = false;
        }
    }

    // 生成棋盘
    vector<string> generate(vector<int>& queens, int n) {
        vector<string> board;
        for (int i = 0; i < n; ++i) {
            string row(n, '.');
            row[queens[i]] = 'Q';
            board.push_back(row);
        }
        return board; // 返回生成的棋盘
    }
};

汇编

链接

静态

文章来源:https://blog.csdn.net/Algo_x/article/details/135119177
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。