代码随想录笔记--回溯算法篇

1--回溯算法理论基础

回溯算法本质上是一个暴力搜索 的过程,其常用于解决组合切割子集排列 等问题,其一般模板如下:

cpp 复制代码
void backTracking(参数){
    if(终止条件){
    // 1. 收获结果;
    // 2. return;
    }

    for(..遍历){
        // 1. 处理节点
        // 2. 递归搜索
        // 3. 回溯 // 即撤销对节点的处理
    }
    return;
}

2--组合问题

主要思路:

基于回溯法,暴力枚举 k 个数,需注意回溯弹出元素的操作;

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
public:
    std::vector<std::vector<int>> combine(int n, int k) {
        std::vector<int> path;
        backTracking(n, k, path, 1); // 从第 1 个数开始
        return res;
    }

    void backTracking(int n, int k, std::vector<int> path, int start){
        if(path.size() == k){
            res.push_back(path);
            return;
        }

        for(int i = start; i <= n; i++){
            path.push_back(i);
            backTracking(n, k, path, i + 1); // 递归暴力搜索下一个数
            path.pop_back(); // 回溯
        }
    }

private:
    std::vector<std::vector<int>> res;
};

int main(int argc, char* argv[]){
    int n = 4, k = 2;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combine(n, k);
    for(auto v : res){
        for(int item : v) std::cout << item << " ";
        std::cout << std::endl;
    }
    return 0;
}

3--组合问题的剪枝操作

上题的组合问题中,对于进入循环体 for(int i = start; i <= n; i++):

已选择的元素数量为:path.size()

仍然所需的元素数量为:k - path.size()

剩余的元素集合为:n - i + 1

则为了满足要求,必须满足:k-path.size() <= n - i + 1,即 i <= n - k + path.size() + 1

因此,可以通过以下条件完成剪枝操作:

for(int i = start; i <= i <= n - k + path.size() + 1; i++)

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
public:
    std::vector<std::vector<int>> combine(int n, int k) {
        std::vector<int> path;
        backTracking(n, k, path, 1); // 从第 1 个数开始
        return res;
    }

    void backTracking(int n, int k, std::vector<int> path, int start){
        if(path.size() == k){
            res.push_back(path);
            return;
        }

        for(int i = start; i <= n - k + path.size() + 1; i++){
            path.push_back(i);
            backTracking(n, k, path, i + 1); // 暴力下一个数
            path.pop_back(); // 回溯
        }
    }

private:
    std::vector<std::vector<int>> res;
};

int main(int argc, char* argv[]){
    int n = 4, k = 2;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combine(n, k);
    for(auto v : res){
        for(int item : v) std::cout << item << " ";
        std::cout << std::endl;
    }
    return 0;
}

4--组合总和III

主要思路:

类似于上面的组合问题,基于回溯来暴力枚举每一个数,需要注意剪枝操作;

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
public:
    std::vector<std::vector<int>> combinationSum3(int k, int n) {
        std::vector<int> path;
        backTracking(k, n, 0, path, 1);
        return res;
    }

    void backTracking(int k, int n, int sum, std::vector<int>path, int start){
        if(sum > n) return; // 剪枝
        if(path.size() == k){ // 递归终止
            if(sum == n){
                res.push_back(path);
            }
            return;
        }

        for(int i = start; i <= 9 + path.size() - k + 1; i++){ // 剪枝
            path.push_back(i);
            sum += i;
            backTracking(k, n, sum, path, i + 1); // 递归枚举下一个数
            // 回溯
            sum -= i;
            path.pop_back();
        }

    }
private:
    std::vector<std::vector<int>> res;
};

int main(int argc, char* argv[]){
    int k = 3, n = 7;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combinationSum3(k, n);
    for(auto v : res){
        for(int item : v) std::cout << item << " ";
        std::cout << std::endl;
    }
    return 0;
}

5--电话号码的字母组合

主要思路:

根据传入的字符串,遍历每一个数字字符,基于回溯法,递归遍历每一个数字字符对应的字母;

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>

class Solution {
public:
    std::vector<std::string> letterCombinations(std::string digits) {
        if(digits.length() == 0) return res;
        std::vector<std::string> letter = {"", " ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        backTracking(digits, letter, 0);
        return res;
    }

    void backTracking(std::string digits, std::vector<std::string> letter, int cur){
        if(cur == digits.size()){
            res.push_back(tmp);
            return;
        }
        int letter_idx = digits[cur] - '0';
        int letter_size = letter[letter_idx].size();
        for(int i = 0; i < letter_size; i++){
            tmp.push_back(letter[letter_idx][i]);
            backTracking(digits, letter, cur+1);
            // 回溯
            tmp.pop_back();
        }
    }

private:
    std::vector<std::string> res;
    std::string tmp;
};

int main(int argc, char* argv[]){
    std::string test = "23";
    Solution S1;
    std::vector<std::string> res = S1.letterCombinations(test);
    for(std::string str : res) std::cout << str << " ";
    std::cout << std::endl;
    return 0;
}

6--组合总和

主要思路:

经典组合问题,通过回溯法暴力递归遍历;

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
public:
    std::vector<std::vector<int>> combinationSum(std::vector<int>& candidates, int target) {
        if(candidates.size() == 0) return res;
        backTracking(candidates, target, 0, 0, path);
        return res;
    }

    void backTracking(std::vector<int>& candidates, int target, int sum, int start, std::vector<int>path){
        if(sum > target) return; // 剪枝
        if(sum == target) {
            res.push_back(path);
            return;
        }

        for(int i = start; i < candidates.size(); i++){
            sum += candidates[i];
            path.push_back(candidates[i]);
            backTracking(candidates, target, sum, i, path);
            // 回溯
            sum -= candidates[i];
            path.pop_back();
        }
    }

private:
    std::vector<std::vector<int>> res;
    std::vector<int> path;
};

int main(int argc, char* argv[]){
    // candidates = [2,3,6,7], target = 7
    std::vector<int> test = {2, 3, 6, 7};
    int target = 7;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combinationSum(test, target);

    for(auto vec : res){
        for(int val : vec) std::cout << val << " ";
        std::cout << std::endl; 
    }

    return 0;
}

7--组合总和II

主要思路:

基于回溯法,暴力递归遍历数组;

本题不能包含重复的组合,但由于有重复的数字,因此需要进行树层上的去重

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

class Solution {
public:
    std::vector<std::vector<int>> combinationSum2(std::vector<int>& candidates, int target) {
        if(candidates.size() == 0) return res;
        std::sort(candidates.begin(), candidates.end());
        std::vector<bool> visited(candidates.size(), false);
        backTracking(candidates, target, 0, 0, visited);
        return res;
    }

    void backTracking(std::vector<int>& candidates, int target, int sum, int start, std::vector<bool>& visited){
        if(sum > target) return; // 剪枝
        if(sum == target){
            res.push_back(path);
            return;
        }
        for(int i = start; i < candidates.size(); i++){
            // 树层剪枝去重
            if(i > 0 && candidates[i-1] == candidates[i]){
                if(visited[i-1] == false) continue;
            }
            sum += candidates[i];
            path.push_back(candidates[i]);
            visited[i] = true;
            backTracking(candidates, target, sum, i+1, visited);
            // 回溯
            sum -= candidates[i];
            path.pop_back();
            visited[i] = false;
        }
    }

private:
    std::vector<std::vector<int>> res;
    std::vector<int> path;
};

int main(int argc, char* argv[]){
    // candidates = [10,1,2,7,6,1,5], target = 8
    std::vector<int> test = {10, 1, 2, 7, 6, 1, 5};
    int target = 8;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combinationSum2(test, target);

    for(auto vec : res){
        for(int val : vec) std::cout << val << " ";
        std::cout << std::endl; 
    }

    return 0;
}

8--

相关推荐
计算机小白一个6 小时前
蓝桥杯 Java B 组之设计 LRU 缓存
java·算法·蓝桥杯
万事可爱^7 小时前
HDBSCAN:密度自适应的层次聚类算法解析与实践
算法·机器学习·数据挖掘·聚类·hdbscan
大数据追光猿9 小时前
Python应用算法之贪心算法理解和实践
大数据·开发语言·人工智能·python·深度学习·算法·贪心算法
Dream it possible!9 小时前
LeetCode 热题 100_在排序数组中查找元素的第一个和最后一个位置(65_34_中等_C++)(二分查找)(一次二分查找+挨个搜索;两次二分查找)
c++·算法·leetcode
夏末秋也凉9 小时前
力扣-回溯-46 全排列
数据结构·算法·leetcode
南宫生9 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
柠石榴9 小时前
【练习】【回溯No.1】力扣 77. 组合
c++·算法·leetcode·回溯
Leuanghing9 小时前
【Leetcode】11. 盛最多水的容器
python·算法·leetcode
qy发大财9 小时前
加油站(力扣134)
算法·leetcode·职场和发展
王老师青少年编程9 小时前
【GESP C++八级考试考点详细解读】
数据结构·c++·算法·gesp·csp·信奥赛